Commit 1aec68cb by ramdayalmunda

css included

parent 5c44509d
.a-doc-editor { .main {
position: relative;
display: flex; display: flex;
width: 100%;
height: 100%;
overflow: hidden;
background-color: #858585;
flex-direction: column; flex-direction: column;
height: 520px; justify-content: flex-start;
}
.a-doc-editor canvas:focus-visible {
outline: auto rgb(0, 68, 255, 50%);
background: #fff;
} }
.header {
.a-doc-editor .header {
background-color: #f0f0f0;
padding: 3px;
cursor:default;
}
.a-doc-editor .dropdown-label {
display: flex;
flex-direction: column;
text-align: center;
justify-content: center;
}
.a-doc-editor .dropdown-list {
display: none;
position: absolute;
z-index: 100;
background-color: #f5f5f5;
border-radius: 5px;
max-height: 300px;
overflow: auto;
top: 100%;
}
.a-doc-editor .dropdown-list.show {
display: block;
width: max-content;
}
.a-doc-editor .dropdown-list .dropdown-option{
padding: 10px;
border-radius: 5px;
}
.a-doc-editor .dropdown-list .dropdown-option:hover{
background: #91d8bd;
}
.a-doc-editor .header .option-bar{
display:flex;
}
.a-doc-editor .header .option{
position: relative; position: relative;
border-bottom: 1px solid rgba(0, 0, 0, 0.137);
padding: 1px;
display: flex;
}
.a-doc-editor .header .option .list-label{
position: relative;
font-size: 0.5em;
border-bottom: 1px solid rgba(0, 0, 0, 0.137);
padding: 1px;
display: flex;
flex-direction: column;
width: 25px;
}
.a-doc-editor .header .option .thumbnail-options{
display: none;
position: absolute;
z-index: 100;
background-color: #f5f5f5;
border-radius: 5px;
max-height: 300px;
overflow: auto;
top: 100%;
}
.a-doc-editor .header .option .thumbnail-options.show {
display: block;
width: max-content;
}
.a-doc-editor .header button{
width: 25px;
}
.a-doc-editor .header .option-button{
width: 25px;
display: flex;
flex-direction: column;
text-align: center;
justify-content: center;
}
.a-doc-editor .header .option input{
width: 25px;
}
.a-doc-editor .header .option .font-color{
display: flex;
flex-direction: column;
text-align: center;
}
.a-doc-editor .header .option .color-bar{
display: block; display: block;
width: 20px;
height: 8px;
background-color: green;
}
.a-doc-editor .header .option .font-color input{
position: absolute;
width: 100%; width: 100%;
height: 100%; background-color: red;
opacity: 0;
}
.a-doc-editor .header .option:hover{
padding: 1px;
background-color: white;
cursor: pointer;
}
.a-doc-editor .header .option.selected{
padding: 1px;
background-color: #91d8bd;
cursor: pointer;
}
.a-doc-editor .footer {
overflow: auto;
background-color: #91d8bd;
} }
.a-doc-editor .body { .toolbar {
flex: 1;
position: relative; position: relative;
background-color: #ccc; width: 100%;
overflow: hidden; background: pink;
transition: 300ms;
}
.a-doc-editor .body .left-sidebar {
left: 0;
border-radius: 0 10px 10px 0;
box-shadow: 4px 0px 10px 4px rgba(0, 0, 0, 0.4);
display: flex; display: flex;
flex-direction: column; gap: 10px;
position: absolute;
padding: 8px;
width: 17%;
top: 0;
bottom: 0;
background-color: #8a9a9b;
transition: 300ms;
}
.a-doc-editor .body .left-sidebar.hide {
left: -16%;
transition: 300ms;
} }
.a-doc-editor .body .toggle-sidebar { .toolbar .item {
position: relative;
background: #408640;
border: 1px solid gray;
border-radius: 3px;
padding-top: 2px;
padding-bottom: 2px;
padding-left: 4px;
padding-right: 4px;
margin: 4px;
cursor: pointer; cursor: pointer;
display: flex;
justify-content: space-between;
} }
.popover{
.a-doc-editor .body .right-sidebar {
right: 0;
border-radius: 10px 0 0 10px;
box-shadow: -4px 0px 10px 4px rgba(0, 0, 0, 0.4);
display: flex;
flex-direction: column;
position: absolute; position: absolute;
padding: 8px; padding: 5px;
width: 17%; background-color: gray;
top: 0; border: 1px solid purple;
bottom: 0; opacity: 0;
background-color: #8a9a9b; pointer-events: none;
transition: 300ms;
} }
.popover.show{
.a-doc-editor .body .right-sidebar.hide { opacity: 1;
right: -16%; pointer-events: auto;
transition: 300ms; z-index: 10;
} }
.a-doc-editor .body .sidebar-body{ .toolbar .item:hover {
flex: 1; background: #5fad5f;
padding: 8px;
text-align: center;
overflow: auto;
display: flex;
flex-direction: column;
} }
.a-doc-editor .body .hide .sidebar-body{ [adc-type="popover"] {
overflow: hidden; pointer-events: none;
} }
.small-input {
.a-doc-editor .body .scrolling-area { width: 50px;
position: absolute;
padding: 8px;
left: 5%;
right: 5%;
top: 0;
bottom: 0;
text-align: center;
overflow: auto;
} }
.small-btn {
.a-doc-editor .body .scrolling-area canvas.page { width: 25px;
background: #f3f3f3;
margin-bottom: 10px;
width: 100%;
box-shadow: 4px 0px 10px 4px rgba(0, 0, 0, 0.4);
} }
.option {
.a-doc-editor .body .scrolling-area canvas.page:first-child { border: 1px solid black;
margin-top: 10px; padding: 5px;
margin: 5px;
border-radius: 4px;
} }
.option:hover {
.a-doc-editor ::-webkit-scrollbar { background-color: #f5c468;
width: 10px;
/* Set the width of the scrollbar */
} }
.page-list {
/* Thumb */ position: relative;
.a-doc-editor ::-webkit-scrollbar-thumb { display: block;
background-color: #888; overflow-y: auto;
/* Set the color of the scrollbar thumb */ overflow-x: auto;
border-radius: 5px; background: #858585;
/* Set border radius */ gap: 20px;
align-items: center;
} }
/* Track */ .page-list canvas {
.a-doc-editor ::-webkit-scrollbar-track { background-color: #fff;
background-color: #f0f0f0; width: 210mm;
/* Set the color of the scrollbar track */ height: auto;
border-radius: 5px; display: block;
/* Set border radius */ position: relative;
margin: 20px;
} }
.full-screen-overlay { .footer {
position: fixed; position: relative;
top: 0; display: block;
left: 0; width: 100%;
height: 100vh; background-color: red;
width: 100vw;
background-color: rgba(0, 0, 0, 0.7); /* Optional background color */
display: flex;
justify-content: center;
align-items: center;
color: white;
font-size: xx-large;
} }
\ No newline at end of file
let isModule = (typeof module != 'undefined') ? true : false let isModule = (typeof module != 'undefined') ? true : false
if (!isModule) console.log('Browser Environment')
var ADocEditor = function (customConfig) { var ADocEditor = function (customConfig) {
var fs, path; var container, counter = 0, shadow, pxMmRatio, canvasPxMmRatio, config, htmlStr, htmlTag, htmlObj = {}, fontList = [], headerToolbar = [];
var PDFDocument, rgb; var paperSizes = {
if (isModule) { "A4": { mmWidth: 210, mmHeight: 297 },
fs = require("fs"); }
path = require("path") const letters = { "0": "a","1": "b","2": "c","3": "d","4": "e","5": "f","6": "g","7": "h","8": "i","9": "j","a": "k","b": "l","c": "m","d": "n","e": "o","f": "p","g": "q","h": "r","i": "s","j": "t","k": "u","l": "v","m": "w","n": "x","o": "y","p": "z" }
} else { const romanNumerals = [ ["I", "IV", "V", "IX"], ["X", "XL", "L", "XC"], ["C", "CD", "D", "CM"], ["M"] ];
var { PDFDocument, rgb } = PDFLib
}
var styleTag;
var screenWidth = (typeof screen != 'undefined') ? screen.width : 1280
var counter = 7
var container = null
var mainComponent = null
var scrollingComponent = null
var headerComponent = null
var footerComponent = null
var leftSidebar = null
var rightSidebar = null
var fontFamily = null;
var fontFamilyDropdown = null
var fontList = []
var fontSize;
var fontColor;
var fontColorLabel;
var fontBold;
var fontItalic;
var fullScreenLoadingOverlay;
var defaultConfig = { var defaultConfig = {
element: "", pageSetup: { size: "A4" }, zoom: 1,
pageSetup: {
width: 210,
height: 297,
orientation: "p",
},
format: { format: {
background: "#fff", background: "#fff",
margin: 20, margin: 15, // mm
border: "", border: "",
fontSize: 10, tabWidth: 20, // mm
tabWidth: 20,
}, },
style: { style: {
fontSize: 16, fontSize: 10, // this is in mm
fontFamily: 'Calibri', fontFamily: 'Calibri',
bold: false, bold: false,
italic: false, italic: false,
fontColor: "#001" fontColor: "#f01"
}, },
} }
var config = JSON.parse(JSON.stringify(defaultConfig)); config = JSON.parse(JSON.stringify(defaultConfig))
var canvasList = [] var dataList = [
var lastFocusCanvas = null;
const dataTypes = [
"paragraph", // simple text filled line by line
"list", // sequence of line that preserves indentation
]
var dataSet = [
{ {
id: ++counter, id: ++counter,
type: 0, type: 0,
formatedText: [], formatedText: [],
plainContent: "", plainContent: "",
tabCount: 0, tabCount: 0,
style: { ...config.style } style: { ...config.style },
listItemNumber: 0,
// newPage: false, // if this is true the data is send to new page
}, },
] ]
// // this is the combination of paragraph and list //
// dataSet=[{id:++counter,type:0,style:{...config.style},formatedText:[],plainContent:"Sarah: Alex, we need some groceries from the store. Could you please go and get them?"},{id:++counter,type:0,style:{...config.style},formatedText:[],plainContent:"Alex: (Without looking up from his laptop) Mom, I'm right in the middle of something important. Can't it wait?"},{id:++counter,type:0,style:{...config.style},formatedText:[],plainContent:"Sarah: (Smiling) I understand, dear. But I've got a lot to do here, and it'd be a great help if you could pick up the groceries."},{id:++counter,type:0,style:{...config.style},formatedText:[],plainContent:"Alex: (Sighing) Can't we go later? I've got this project deadline..."},{id:++counter,type:0,style:{...config.style},formatedText:[],plainContent:"Sarah: (Interrupting gently) Alex, we're running low on essentials. It won't take much time. Plus, some fresh air might do you good after being glued to your laptop for hours."},{id:++counter,type:0,style:{...config.style},formatedText:[],plainContent:"Alex: (Reluctantly) But it's your day off, Mom. You should relax."},{id:++counter,type:0,style:{...config.style},formatedText:[],plainContent:"Sarah: (Chuckling) Relaxing is fetching groceries? No worries, it's alright. I can go."},{id:++counter,type:0,style:{...config.style},formatedText:[],plainContent:"Alex: (Feeling guilty) No, no, I'll go. Just let me finish this task, okay?"},{id:++counter,type:0,style:{...config.style},formatedText:[],plainContent:"Sarah: (Smiling warmly) Thanks, dear. I really appreciate it."},{id:++counter,type:0,style:{...config.style},formatedText:[],plainContent:"They share a smile, and Alex closes his laptop, heading out to the store."},{id:++counter,type:0,style:{...config.style},formatedText:[],plainContent:"Sarah: (Calling after him) Don't forget the list!"},{id:++counter,type:0,style:{...config.style},formatedText:[],plainContent:"Alex waves in acknowledgment and leaves, while Sarah returns to her cooking, content that the groceries will soon be sorted. Alex looks at the list, there were a lot of items on the list."},{id:++counter,type:1,style:{...config.style},formatedText:[],plainContent:"Vegetables",tabCount:0},{id:++counter,type:1,style:{...config.style},formatedText:[],plainContent:"Carrots",tabCount:1},{id:++counter,type:1,style:{...config.style},formatedText:[],plainContent:"Broccli",tabCount:1},{id:++counter,type:1,style:{...config.style},formatedText:[],plainContent:"Bell Peppers",tabCount:1},{id:++counter,type:1,style:{...config.style},formatedText:[],plainContent:"Fruits",tabCount:0},{id:++counter,type:1,style:{...config.style},formatedText:[],plainContent:"Apples",tabCount:1},{id:++counter,type:1,style:{...config.style},formatedText:[],plainContent:"Oranges",tabCount:1},{id:++counter,type:1,style:{...config.style},formatedText:[],plainContent:"Dairy",tabCount:0},{id:++counter,type:1,style:{...config.style},formatedText:[],plainContent:"Milk",tabCount:1},{id:++counter,type:1,style:{...config.style},formatedText:[],plainContent:"Cheese",tabCount:1},{id:++counter,type:1,style:{...config.style},formatedText:[],plainContent:"Yogurt",tabCount:1},{id:++counter,type:1,style:{...config.style},formatedText:[],plainContent:"Household",tabCount:0},{id:++counter,type:1,style:{...config.style},formatedText:[],plainContent:"Cottonelle Toilet Paper (Pack of 6). Make sure to bring the items where each pack has 100 meter rolls.",tabCount:1},{id:++counter,type:1,style:{...config.style},formatedText:[],plainContent:"Mrs. Meyer's Lemon Verbena Dish Soap",tabCount:1},{id:++counter,type:1,style:{...config.style},formatedText:[],plainContent:"Tide Free & Gentle Laundry Detergent",tabCount:1},{id:++counter,type:1,style:{...config.style},formatedText:[],plainContent:"Bakery",tabCount:0},{id:++counter,type:1,style:{...config.style},formatedText:[],plainContent:"Bread",tabCount:1},{id:++counter,type:1,style:{...config.style},formatedText:[],plainContent:"Bagels",tabCount:1},{id:++counter,type:0,style:{...config.style},formatedText:[],plainContent:"Once Alex stepped out of his house, he felt the crisp autumn air greet him. Armed with his phone displaying Sarah's detailed grocery list, he set off on his mission to the nearby supermarket."},{id:++counter,type:0,style:{...config.style},formatedText:[],plainContent:"The store was bustling with shoppers, but Alex navigated through the aisles with purpose. He began in the produce section, carefully selecting the freshest carrots, crisp broccoli, and vibrant red bell peppers. He lingered a moment longer in the fruit section, examining each apple, banana, and orange to ensure they met the criteria on Sarah's list."},{id:++counter,type:0,style:{...config.style},formatedText:[],plainContent:"With the vegetables and fruits secured, he made his way to the dairy aisle. He picked up the specified 2% organic milk, sharp cheddar cheese, and Greek vanilla yogurt, double-checking the brands to match Sarah's preferences."},{id:++counter,type:0,style:{...config.style},formatedText:[],plainContent:"Next on the list were bakery items. He chose a loaf of whole-grain bread and a pack of everything bagels, making sure they were fresh before moving on to the pantry essentials. Penne pasta, Basmati rice, fire-roasted diced tomatoes, and honey nut crunch cereal found their way into his cart."},{id:++counter,type:0,style:{...config.style},formatedText:[],plainContent:"As he moved to the meat and protein section, he carefully selected free-range chicken breasts, a pound of lean ground beef, and a dozen farm-fresh eggs, ensuring they met the quality standards Sarah had in mind."},{id:++counter,type:0,style:{...config.style},formatedText:[],plainContent:"After collecting household items like cottonelle toilet paper, Mrs. Meyer's lemon verbena dish soap, and Tide Free & Gentle laundry detergent, Alex made his way to the checkout counter. The cashier scanned each item, and Alex packed them meticulously into bags, arranging delicate produce separately to prevent any damage."},{id:++counter,type:0,style:{...config.style},formatedText:[],plainContent:"Balancing the bags on his arms, Alex made his way back home. The sky had turned a warm orange hue as the sun began to set. He navigated through the busy streets, feeling a sense of accomplishment for successfully checking off every item on Sarah's exhaustive list."},{id:++counter,type:0,style:{...config.style},formatedText:[],plainContent:"As he approached his house, he could already smell the aroma of the dinner Sarah was preparing. Alex walked in, slightly out of breath but proud of his successful grocery run."},{id:++counter,type:0,style:{...config.style},formatedText:[],plainContent:'"Hey, Mom, I\'m back!" he called out.'},{id:++counter,type:0,style:{...config.style},formatedText:[],plainContent:'Sarah, busy at the stove, turned around, beaming. "Oh, Alex, you\'re a lifesaver! Did you manage to get everything?"'},{id:++counter,type:0,style:{...config.style},formatedText:[],plainContent:'"Absolutely," Alex replied, setting the bags down on the counter. "And I even got the specific brands and types you asked for!"'},{id:++counter,type:0,style:{...config.style},formatedText:[],plainContent:'Sarah gave him a grateful smile. "Thank you so much, Alex. You\'re a star! Now, dinner will be ready in no time."'},{id:++counter,type:0,style:{...config.style},formatedText:[],plainContent:"As they unpacked the groceries together, Sarah couldn't help but appreciate her thoughtful son's effort and attention to detail, making the task much more manageable and enjoyable for her."},];
var lines = []
var caretData = { var caretData = {
activeData: dataSet[0], activeData: dataList[0],
index: 0, index: 0,
interval: null, timeout: null,
intervalDuration: 800, timeoutDuration: 800,
blink: false, blink: true,
canvasIndex: 0, pageIndex: 0,
caretSize: config.style.fontSize, caretSize: config.style.fontSize,
x: config.format.margin,
y: config.format.margin + (3 * config.style.fontSize / 4),
previousCaret: null, previousCaret: null,
style: { ...dataSet[0].style }, style: { ...dataList[0].style, x: config.format.margin, y: config.format.margin + (3 * config.style.fontSize / 4) },
} }
var renderInProgress = false; var pageList = []
var pageScrollingDiv = null
function inititalize(customConfig) { var focussedPage = null
config = { ...defaultConfig, ...customConfig } var lastFocussedPage = null
config.pageSetup.canvasMultiplier = Math.round(screenWidth / config.pageSetup.width) var isRendering = false
config.pageSetup.fontMultiplier = config.pageSetup.canvasMultiplier / 3 function initialize() {
config.pageSetup.canvasWidth = config.pageSetup.canvasMultiplier * config.pageSetup.width config = JSON.parse(JSON.stringify(defaultConfig))
config.pageSetup.canvasHeight = config.pageSetup.canvasMultiplier * config.pageSetup.height container = customConfig.container
if (!isModule) { shadow = container.attachShadow({ mode: "open" })
if (!config.element) throw "'container' not provided in the config"
if (typeof config.element == 'string') container = document.querySelector(config.element) htmlStr = /*html*/`
if (config.element instanceof HTMLElement) container = config.element <div class="header">
if (!container) throw "undefined" <div class="toolbar">
<select class="item" adc="font-select"></select>
styleTag = document.createElement('style') <div class="item" adc="list-handler" adc-target="list-popover">
let mainComponentId = `main-${new Date().getTime()}` <span>L</span>
let headerComponentId = `header-${new Date().getTime()}` <div class="popover" adc="list-popover" adc-type="popover">
let footerComponentId = `footer-${new Date().getTime()}` <div class="option" adc-toggle="listing-option" value="1">•&nbsp;&nbsp;Bullets&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</div>
let scrollingAreaId = `scrolling-area-${new Date().getTime()}` <div class="option" adc-toggle="listing-option" value="0">1. Numbers</div>
let leftSidebarId = `left-sidebar-${new Date().getTime()}`
let rightSidebarId = `right-sidebar-${new Date().getTime()}`
let fontFamilyDropdownId = `font-family-${new Date().getTime()}`
let htmlSetup = /*html*/`
<div class="a-doc-editor" id="${mainComponentId}">
<div class="header" id="${headerComponentId}">
<div class="option-bar">
<div class="option">File</div>
<div class="option">Edit</div>
<div class="option">Help</div>
</div> </div>
<div class="option-bar">
<div class="option">
<div class="dropdown-label" ade-target="#${fontFamilyDropdownId}" style="min-width:180px"></div>
<div class="dropdown-list" id="${fontFamilyDropdownId}" ade-type="dropdown"></div>
</div> </div>
<div class="option"> <div class="item">
<button ade-action="font-size-minus">-</button> <button adc="font-size-change" class="small-btn" value="-1">-</button>
<input ade-action="font-size-set"/> <input type="number" adc="font-size-input" class="small-input">
<button ade-action="font-size-plus">+</button> <button adc="font-size-change" class="small-btn" value="+1">+</button>
</div> </div>
<div class="option" ade-action="font-bold">
<div class="option-button"><strong>B</strong></div>
</div> </div>
<div class="option" ade-action="font-italic"> </div>
<div class="option-button"><em>I</em></div> <div class="page-list" adc="page-list"></div>
<div class="footer">
<div class="toolbar">
<select class="item" title="Select zoom" adc="zoom">
<option value="" disabled>Select Zoom</option>
<option value="0.1">10%</option>
<option value="0.2">20%</option>
<option value="0.3">30%</option>
<option value="0.4">40%</option>
<option value="0.5">50%</option>
<option value="0.6">60%</option>
<option value="0.7">70%</option>
<option value="0.8">80%</option>
<option value="0.9">90%</option>
<option value="1" selected>100%</option>
<option value="1.2">120%</option>
<option value="1.5">150%</option>
<option value="1.8">180%</option>
<option value="2">200%</option>
</select>
<span class="item">Words : 0</span>
<span class="item">Sentences : 0</span>
<span class="item">Pages : 1</span>
</div> </div>
</div>
`;
<div class="option">
<div class="font-color">
<span>A</span>
<span class="color-bar" ade-label="font-color"></span>
<input type="color" ade-action="font-color">
</div>
</div>
<div class="option"> let styleSheets = customConfig?.styleSheet?.length ? customConfig.styleSheet : []
<div class="list-label"> styleSheets.splice(0, 0, "/assets/a-doc-editor.css")
<span>1&ndash;&ndash;&ndash;&ndash;</span> for (let i = 0; i < styleSheets.length; i++) {
<span>2&ndash;&ndash;&ndash;&ndash;</span> let link = document.createElement('link')
<span>3&ndash;&ndash;&ndash;&ndash;</span> link.setAttribute('rel', 'stylesheet');
</div> link.setAttribute('href', styleSheets[i]);
<div class="thumbnail-options"> shadow.append(link)
<div>Item 1</div> }
<div>Item 2</div>
<div>Item 3</div>
<div>Item 4</div>
<div>Item 5</div>
<div>Item 6</div>
<div>Item 7</div>
<div>Item 8</div>
<div>Item 9</div>
</div>
</div>
</div> htmlTag = document.createElement('div')
</div> htmlTag.setAttribute('class', 'main')
<div class="body"> htmlTag.innerHTML = htmlStr
<div class="scrolling-area" id="${scrollingAreaId}"> shadow.append(htmlTag)
</div>
<div class="left-sidebar" id="${leftSidebarId}"> htmlObj = {
<div class="toggle-sidebar"> header: shadow.querySelector('.header'),
<span></span> pageList: shadow.querySelector('page-list')
<span>Summary</span> }
<span>&lrarr;</span> pageScrollingDiv = shadow.querySelector('[adc="page-list"]')
</div>
<div class="sidebar-body"></div>
</div>
<div class="right-sidebar" id="${rightSidebarId}">
<div class="toggle-sidebar">
<span>&lrarr;</span>
<span>Comments</span>
<span></span>
</div>
<div class="sidebar-body"></div>
</div>
</div>
<div class="footer" id="${footerComponentId}">
<div>Footer Data</div>
</div>
</div>
`;
!(function zoomHandles() {
let zoomSelect = shadow.querySelector('[adc="zoom"]')
zoomSelect.addEventListener('change', (e) => {
config.zoom = Number(e.target.value)
reConfigure(config)
})
})()
!(function fontSelectHandle() {
let fontSelect = shadow.querySelector('[adc="font-select"]')
headerToolbar.push(fontSelect)
fontSelect.addEventListener('change', (e) => {
caretData.style = {
...caretData.style,
fontFamily: e.target.value,
}
focusOnPage()
})
addFonts(["./assets/fonts/Afacad-VariableFont_wght.woff2", "./assets/fonts/Afacad-VariableFont_wght.ttf"], 'Afacad') addFonts(["./assets/fonts/Afacad-VariableFont_wght.woff2", "./assets/fonts/Afacad-VariableFont_wght.ttf"], 'Afacad')
addFonts(["./assets/fonts/ArchitectsDaughter-Regular.woff2", "./assets/fonts/ArchitectsDaughter-Regular.ttf"], 'Architects Daughter') addFonts(["./assets/fonts/ArchitectsDaughter-Regular.woff2", "./assets/fonts/ArchitectsDaughter-Regular.ttf"], 'Architects Daughter')
addFonts(["./assets/fonts/Assistant-VariableFont_wght.woff2", "./assets/fonts/Assistant-VariableFont_wght.ttf"], 'Assistant') addFonts(["./assets/fonts/Assistant-VariableFont_wght.woff2", "./assets/fonts/Assistant-VariableFont_wght.ttf"], 'Assistant')
addFonts(["./assets/fonts/Bitter-VariableFont_wght.woff2", "./assets/fonts/Bitter-VariableFont_wght.ttf"], 'Bitter') addFonts(["./assets/fonts/Bitter-VariableFont_wght.woff2", "./assets/fonts/Bitter-VariableFont_wght.ttf"], 'Bitter')
addFonts(["./assets/fonts/calibri-regular.woff2", "./assets/fonts/calibri-regular.ttf"], 'Calibri') addFonts(["./assets/fonts/calibri-regular.woff2", "./assets/fonts/calibri-regular.ttf"], 'Calibri')
addFonts(["./assets/fonts/Caveat-VariableFont_wght.woff2", "./assets/fonts/Caveat-VariableFont_wght.ttf"], 'Caveat')
addFonts(["./assets/fonts/Comfortaa-VariableFont_wght.woff2", "./assets/fonts/Comfortaa-VariableFont_wght.ttf"], 'Comfortaa')
addFonts(["./assets/fonts/CrimsonText-Regular.woff2", "./assets/fonts/CrimsonText-Regular.ttf"], 'Crimson Text')
addFonts(["./assets/fonts/EduTASBeginner-VariableFont_wght.woff2", "./assets/fonts/EduTASBeginner-VariableFont_wght.ttf"], 'Edu TAS Beginner')
addFonts(["./assets/fonts/FiraSans-Regular.woff2", "./assets/fonts/FiraSans-Regular.ttf"], 'Fira Sans')
addFonts(["./assets/fonts/IndieFlower-Regular.woff2", "./assets/fonts/IndieFlower-Regular.ttf"], 'Indie Flower')
addFonts(["./assets/fonts/Kanit-Regular.woff2", "./assets/fonts/Kanit-Regular.ttf"], 'Kanit')
addFonts(["./assets/fonts/Karla-VariableFont_wght.woff2", "./assets/fonts/Karla-VariableFont_wght.ttf"], 'Karla')
addFonts(["./assets/fonts/Kenia-Regular.woff2", "./assets/fonts/Kenia-Regular.ttf"], 'Kenia')
addFonts(["./assets/fonts/Lato-Regular.woff2", "./assets/fonts/Lato-Regular.ttf"], 'Lato')
addFonts(["./assets/fonts/LibreFranklin-VariableFont_wght.woff2", "./assets/fonts/LibreFranklin-VariableFont_wght.ttf"], 'Libre Franklin')
addFonts(["./assets/fonts/Lora-Regular.woff2", "./assets/fonts/Lora-Regular.ttf"], 'Lora')
addFonts(["./assets/fonts/Macondo-Regular.woff2", "./assets/fonts/Macondo-Regular.ttf"], 'Macondo')
addFonts(["./assets/fonts/Merriweather-Regular.woff2", "./assets/fonts/Merriweather-Regular.ttf"], 'Merriweather')
addFonts(["./assets/fonts/Montserrat-VariableFont_wght.woff2", "./assets/fonts/Montserrat-VariableFont_wght.ttf"], 'Montserrat')
addFonts(["./assets/fonts/Mulish-VariableFont_wght.woff2", "./assets/fonts/Mulish-VariableFont_wght.ttf"], 'Mulish')
addFonts(["./assets/fonts/NotoSans-VariableFont_wdth,wght.woff2", "./assets/fonts/NotoSans-VariableFont_wdth,wght.ttf"], 'Noto Sans')
addFonts(["./assets/fonts/Nunito-VariableFont_wght.woff2", "./assets/fonts/Nunito-VariableFont_wght.ttf"], 'Nunito')
addFonts(["./assets/fonts/OpenSans-VariableFont_wdth,wght.woff2", "./assets/fonts/OpenSans-VariableFont_wdth,wght.ttf"], 'Open Sans')
addFonts(["./assets/fonts/Outfit-VariableFont_wght.woff2", "./assets/fonts/Outfit-VariableFont_wght.ttf"], 'Outfit')
addFonts(["./assets/fonts/Pacifico-Regular.woff2", "./assets/fonts/Pacifico-Regular.ttf"], 'Pacifico')
addFonts(["./assets/fonts/Poppins-Regular.woff2", "./assets/fonts/Poppins-Regular.ttf"], 'Poppins')
addFonts(["./assets/fonts/Prompt-Regular.woff2", "./assets/fonts/Prompt-Regular.ttf"], 'Prompt')
addFonts(["./assets/fonts/Rajdhani-Regular.woff2", "./assets/fonts/Rajdhani-Regular.ttf"], 'Rajdhani')
addFonts(["./assets/fonts/Roboto-Regular.woff2", "./assets/fonts/Roboto-Regular.ttf"], 'Roboto')
addFonts(["./assets/fonts/Rubik-VariableFont_wght.woff2", "./assets/fonts/Rubik-VariableFont_wght.ttf"], 'Rubik')
addFonts(["./assets/fonts/SourceCodePro-VariableFont_wght.woff2", "./assets/fonts/SourceCodePro-VariableFont_wght.ttf"], 'Source Code Pro')
addFonts(["./assets/fonts/Teko-VariableFont_wght.woff2", "./assets/fonts/Teko-VariableFont_wght.ttf"], 'Teko')
addFonts(["./assets/fonts/TitilliumWeb-Regular.woff2", "./assets/fonts/TitilliumWeb-Regular.ttf"], 'Titillium Web')
addFonts(["./assets/fonts/Ubuntu-Regular.woff2", "./assets/fonts/Ubuntu-Regular.ttf"], 'Ubuntu')
addFonts(["./assets/fonts/VarelaRound-Regular.woff2", "./assets/fonts/VarelaRound-Regular.ttf"], 'Varela Round')
addFonts(["./assets/fonts/WorkSans-Regular.woff2", "./assets/fonts/WorkSans-Regular.ttf"], 'Work Sans')
container.innerHTML = htmlSetup
container.append(styleTag)
mainComponent = document.getElementById(mainComponentId)
headerComponent = document.getElementById(headerComponentId)
scrollingComponent = document.getElementById(scrollingAreaId)
footerComponent = document.getElementById(footerComponentId)
leftSidebar = document.getElementById(leftSidebarId)
rightSidebar = document.getElementById(rightSidebarId)
leftSidebar.classList.add('hide')
rightSidebar.classList.add('hide')
mainComponent.getElementsByClassName('toggle-sidebar')[0].addEventListener('click', (e) => {
leftSidebar.classList.toggle('hide')
})
mainComponent.getElementsByClassName('toggle-sidebar')[1].addEventListener('click', (e) => {
rightSidebar.classList.toggle('hide')
})
fontFamily = container.querySelector(`[ade-target="#${fontFamilyDropdownId}"]`)
fontFamilyDropdown = document.getElementById(fontFamilyDropdownId)
fontFamilyDropdown.addEventListener('click', changeFontFamily)
reRenderFontDropdown()
fontSize = container.querySelector('[ade-action="font-size-set"]')
fontColor = container.querySelector('[ade-action="font-color"]') })()
fontColor.addEventListener('input', changeFontColor)
fontColorLabel = container.querySelector('[ade-label="font-color"]') !(function handleList() {
let listItems = shadow.querySelectorAll('[adc-toggle="listing-option"]')
fontBold = container.querySelector('[ade-action="font-bold"]') let popover = shadow.querySelector('[adc-type="popover"]')
fontBold.addEventListener('click', toggleBold) for(let i=0; i<listItems.length; i++){
fontItalic = container.querySelector('[ade-action="font-italic"]') listItems[i].addEventListener('click', (e) => {
fontItalic.addEventListener('click', toggleItalic) let value = Number(e.target.getAttribute('value'))
value = value?value:0
if (caretData.activeData) {
caretData.activeData.type = (caretData.activeData.type == 1 && caretData.activeData.listStyle==value) ? 0 : 1
if (caretData.activeData.type==1){
caretData.activeData.listStyle = value
}
changeListStyle()
caretData.blink = false
reRenderCanvas()
popover.classList.remove('show')
focusOnPage()
fullScreenLoadingOverlay = document.createElement('div') }
fullScreenLoadingOverlay.innerText = 'Loading...' })
fullScreenLoadingOverlay.setAttribute('class', 'full-screen-overlay') }
})()
!(function fontSizeHandler(){
let fontSizeInput = shadow.querySelector('[adc="font-size-input"]')
fontSizeInput.value = caretData.style.fontSize
let fontSizeChangers = shadow.querySelectorAll('[adc="font-size-change"]')
fontSizeChangers.forEach( btn => {
btn.addEventListener( 'click',(e)=>{
let change = Number(e.target.getAttribute('value'))
change = change?change:0
change = caretData.style.fontSize + change
change = change?change:config.style.fontSize
caretData.style.fontSize = change
fontSizeInput.value = change
focusOnPage()
} )
} )
function changeFontEvent(e){
let value = Number( e.target.value )
value = value?value:config.style.fontSize
caretData.style.fontSize = value
if (e?.key == 'Enter') focusOnPage()
}
fontSizeInput.addEventListener('keydown', changeFontEvent)
fontSizeInput.addEventListener('input', changeFontEvent)
bindGlobalEvents() })()
reConfigure(customConfig)
reRenderCanvas()
addGlobalEvents()
}
function reConfigure(newConfig) {
if (newConfig?.size && paperSizes[newConfig?.size]) { config.pageSetup = { ...paperSizes[newConfig.size], size: newConfig.size } }
else if (newConfig?.width && newConfig?.height) {
config.pageSetup = { size: "Custom", mmWidth: newConfig.width, mmHeight: newConfig.height }
} }
else { config.pageSetup = { size: "A4", ...paperSizes['A4'] } }
if (typeof window == 'object') {
config.pageSetup.multipler = config.pageSetup.mmHeight / config.pageSetup.mmWidth
config.pageSetup.pxWidth = 2480
config.pageSetup.pxHeight = Math.ceil(config.pageSetup.pxWidth * config.pageSetup.multipler)
} else {
config.pageSetup.pxHeight = config.pageSetup.mmHeight
config.pageSetup.pxWidth = config.pageSetup.mmWidth
}
reRenderPages(dataSet) if (config.zoom) {
config.pageSetup.uiWidth = config.pageSetup.mmWidth * config.zoom
config.pageSetup.uiHeight = config.pageSetup.mmHeight * config.zoom
} }
function reRenderPages(dataList, option) { for (let p = 0; p < pageList.length; p++) {
pageList[p].el.style.width = `${config.pageSetup.uiWidth}mm`
}
if (renderInProgress) return pxMmRatio = config.pageSetup.pxWidth / config.pageSetup.mmWidth;
renderInProgress = true return config
}
let canvasIndex = 0 function reRenderCanvas() {
if (!canvasList.length) canvasList[0] = { el: createNewCanvas(), dataIndex: 0 } if (isRendering) return
let pageIndex = 0
if (!pageList.length) {
pageList[pageIndex] = { id: ++counter, el: createNewPage(), dataIndex: 0 }
shadow.querySelector('.page-list').append(pageList[pageIndex].el)
}
// to clear the canvases // to clear the canvases
for (let i = 0; i < canvasList.length; i++) { for (let i = 0; i < pageList.length; i++) {
let ctx = canvasList[i].el.getContext('2d', { willReadFrequently: true }) let ctx = pageList[i].el.getContext('2d', { willReadFrequently: true })
ctx.save() ctx.save()
ctx.clearRect(0, 0, canvasList[i].el.width, canvasList[i].el.height); // clears the canvas ctx.clearRect(0, 0, pageList[i].el.width, pageList[i].el.height); // clears the canvas
ctx.fillStyle = config.format.background ctx.fillStyle = config.format.background
ctx.fillRect(0, 0, canvasList[i].el.width, canvasList[i].el.height) ctx.fillRect(0, 0, pageList[i].el.width, pageList[i].el.height)
ctx.restore() ctx.restore()
} }
lines = []
for (let i = 0; i < dataList.length; i++) {
calculateTextSizeAndPosition(canvasIndex, i)
}
renderTheLines()
if (!caretData.blink && caretData.activeData) renderCaret()
lines = []
function calculateTextSizeAndPosition(canvasIndex, dataSetIndex) { calculateTextSizeAndPosition()
let dataBlock = dataList[dataSetIndex] renderLines()
let canvas = canvasList[canvasIndex].el caretData.previousCaret = null
let ctx = canvas.getContext('2d', { willReadFrequently: true }) renderCaret(true)
ctx.save()
// to calculate the lines function calculateTextSizeAndPosition() {
let dataLineArr = [] let d = 0, c = 0;
function getLineObj() { function getLineObj() {
let newLineObj = { let newLineObj = {
...config.style, ...config.style,
x: 0, // this is the starting point x; it will change based on the tabNumber x: 0, // this is the starting point x; it will change based on the tabNumber
y: 0, // this is the starting y coordinate; it will change based on the max font size y: 0, // this is the starting y coordinate; it will change based on the max font size
plainContent: "", plainContent: "",
dataIndex: d,
dataSetIndex: dataSetIndex,
charStartIndex: 0, // index from where to check charStartIndex: 0, // index from where to check
charEndIndex: 0, // index till where to check// not including this index. charEndIndex: 0, // index till where to check// not including this index.
listItemNumber: 0,
} }
newLineObj.maxFontSize = newLineObj.fontSize newLineObj.maxFontSize = newLineObj.fontSize
return newLineObj return newLineObj
} }
if (!dataBlock.formatedText) dataBlock.formatedText = [] for (d = 0; d < dataList.length; d++) {
let lineObj = new getLineObj() let lineObj = getLineObj()
dataLineArr.push(lineObj) let dataBlock = dataList[d]
lineObj.type = dataBlock.type let canvas = pageList[pageIndex].el
lineObj.blockStart = true let ctx = canvas.getContext('2d', { willReadFrequently: true })
let wordEndIndex = 0; // this stores the index of the word which can fit in the line; ctx.save()
let tempLineWidth = 0;
let maxLineWidth = config.pageSetup.canvasWidth - (config.format.margin * 2 * config.pageSetup.canvasMultiplier)
if (dataBlock.tabCount || dataBlock.type == 1) {
let finalTabCount = dataBlock.tabCount
if (dataBlock.type == 1) {
finalTabCount++
}
let tabDistance = finalTabCount * config.format.tabWidth * config.pageSetup.canvasMultiplier
if (tabDistance > maxLineWidth * 5 / 6) { tabDistance = 0 }
maxLineWidth = maxLineWidth - tabDistance
}
lineObj.maxLineWidth = maxLineWidth
lineObj.tabCount = dataBlock.tabCount
// // for checking the listIndex // to calculate the lines
let tempLineWidth = 0;
let maxLineWidth = config.pageSetup.pxWidth - (config.format.margin * pxMmRatio * 2)
let tabWidth = ((dataBlock.type == 1 ? 1 : 0) + dataBlock.tabCount) * config.format.tabWidth * pxMmRatio
lineObj.tabWidth = tabWidth
maxLineWidth -= tabWidth
lineObj.dataType = dataBlock.type
let wordEndIndex = 0; // this stores the index of the word which can fit in the line;
let tempWordWidth = 0
if (dataBlock.type == 1) { if (dataBlock.type == 1) {
for (let i = dataSetIndex - 1; i >= 0; i--) { let previousBlock = dataList[d - 1]
if (dataList[i].type != 1) { if (!previousBlock || previousBlock?.type != 1) dataBlock.listItemNumber = 1
dataBlock.listIndex = 0 else if (previousBlock.tabCount == dataBlock.tabCount) {
break; dataBlock.listItemNumber = previousBlock.listItemNumber + 1
} else if (dataList[i].tabCount == dataBlock.tabCount) { } else {
dataBlock.listIndex = dataList[i].listIndex + 1 dataBlock.listItemNumber = 1
break let olderBlockIndex = d - 1
} else if (dataList[i].tabCount < dataBlock.tabCount) { while (dataList?.[olderBlockIndex]?.type == 1 && dataList?.[olderBlockIndex]?.tabCount >= dataBlock.tabCount) {
dataBlock.listIndex = 0 if (dataList?.[olderBlockIndex]?.tabCount == dataBlock.tabCount) {
dataBlock.listItemNumber = dataList?.[olderBlockIndex].listItemNumber + 1
break; break;
} }
--olderBlockIndex;
}
} }
} }
lineObj.listIndex = dataBlock.listIndex for (c = 0; c < dataBlock.plainContent.length; c++) {
let style = dataBlock?.formatedText?.[c]
lineObj.maxFontSize = (lineObj.maxFontSize<dataBlock?.formatedText?.[c].fontSize)?dataBlock?.formatedText?.[c].fontSize:lineObj.maxFontSize
let i = 0 if (/\s/.test(dataBlock.plainContent[c])) {
for (i = 0; i < dataBlock.plainContent.length; i++) { // condition to check if a blank character is found.
let style = dataBlock?.formatedText?.[i] wordEndIndex = c
if (!style) style = JSON.parse(JSON.stringify(config.style)) lineObj.charEndIndex = c
if (/\s/.test(dataBlock.plainContent[i])) { tempWordWidth = 0
wordEndIndex = i
lineObj.charEndIndex = wordEndIndex;
} }
let charWidth = getCharacterWidth(canvasIndex, dataBlock.plainContent[i], style) let charWidth = getCharacterWidth(dataBlock.plainContent[c], style)
dataBlock.formatedText[i] = {
dataBlock.formatedText[c] = {
...config.style, ...config.style,
...dataBlock.formatedText[i], ...style,
width: charWidth width: charWidth
} }
lineObj.maxFontSize = (!lineObj.maxFontSize)?style.fontSize:( style.fontSize>lineObj.maxFontSize?style.fontSize:lineObj.maxFontSize ) // // if with the current char the string could not fit on one line
// lineObj.fontSize = style.fontSize if (tempLineWidth + charWidth > maxLineWidth) {
tempLineWidth += charWidth
if (tempLineWidth <= maxLineWidth) {
// can be added to the line //
} else {
// cannot add this// new line should be added// // cannot add this// new line should be added//
i = wordEndIndex;
if (tempWordWidth + charWidth >= maxLineWidth) {
// this is to manage the casse where there is a set of long string without any blank space. In that case the text is broken and moved to next line.
wordEndIndex = c
lineObj.charEndIndex = c
tempWordWidth = 0
}
lineObj.plainContent = dataBlock.plainContent.slice(lineObj.charStartIndex, lineObj.charEndIndex + 1) lineObj.plainContent = dataBlock.plainContent.slice(lineObj.charStartIndex, lineObj.charEndIndex + 1)
lines.push(lineObj)
lineObj = new getLineObj() lineObj = new getLineObj()
lineObj.listIndex = dataBlock.listIndex
lineObj.maxLineWidth = maxLineWidth lineObj.maxLineWidth = maxLineWidth
lineObj.maxFontSize = style.fontSize
if (dataBlock.type == 1){
lineObj.tabWidth = tabWidth
lineObj.tabCount = dataBlock.tabCount lineObj.tabCount = dataBlock.tabCount
lineObj.charStartIndex = i }else{
lineObj.charEndIndex = i maxLineWidth = config.pageSetup.pxWidth - (config.format.margin * pxMmRatio * 2)
dataLineArr.push(lineObj) lineObj.tabWidth = 0
tempLineWidth = 0 lineObj.tabCount = 0
}
lineObj.charStartIndex = wordEndIndex + 1
lineObj.charEndIndex = wordEndIndex + 1
tempLineWidth = tempWordWidth
}
else {
// if the char can fit in the same line. then it is well and good
tempLineWidth += charWidth
tempWordWidth += charWidth
} }
} }
lineObj.plainContent = dataBlock.plainContent.slice(lineObj.charStartIndex, lineObj.charEndIndex + 1) lineObj.plainContent = dataBlock.plainContent.slice(lineObj.charStartIndex, lineObj.charEndIndex + 1)
// there is chance that the last line is not at the width// so we need to handle the last line separately // there is chance that the last line is not at the width// so we need to handle the last line separately
if (lineObj.charEndIndex <= dataBlock.plainContent.length) { if (lineObj.charEndIndex <= dataBlock.plainContent.length) {
...@@ -404,65 +374,45 @@ var ADocEditor = function (customConfig) { ...@@ -404,65 +374,45 @@ var ADocEditor = function (customConfig) {
} }
lineObj.plainContent = dataBlock.plainContent.slice(lineObj.charStartIndex, lineObj.charEndIndex) lineObj.plainContent = dataBlock.plainContent.slice(lineObj.charStartIndex, lineObj.charEndIndex)
lines.push(...dataLineArr) lines.push(lineObj)
ctx.restore() ctx.restore()
return true
} }
return
function renderTheLines() {
let canvasIndex = 0
let ctx = canvasList[canvasIndex].el.getContext('2d', { willReadFrequently: true })
let x = 0
let y = (config.format.margin * config.pageSetup.canvasMultiplier)
let maxVericalWidth = (config.pageSetup.canvasHeight - config.format.margin * config.pageSetup.canvasMultiplier * 2); // this is the height of writable area; so this excludes the margin from top and bottom.
for (let l = 0; l < lines.length; l++) {
x = (config.format.margin * config.pageSetup.canvasMultiplier);
y += (lines[l].maxFontSize * config.pageSetup.fontMultiplier)
if (lines[l].blockStart && l != 0) y += lines[l].maxFontSize
if ((maxVericalWidth + lines[l].maxFontSize) < (y - lines[l].maxFontSize)) {
canvasIndex++
if (!canvasList[canvasIndex]) {
canvasList[canvasIndex] = { id: ++counter, el: createNewCanvas(), dataIndex: lines[l].dataSetIndex, lineIndex: l }
}
ctx = canvasList[canvasIndex].el.getContext('2d', { willReadFrequently: true })
y = lines[l].maxFontSize + (config.format.margin * config.pageSetup.canvasMultiplier)
} }
function renderLines() {
let setData = dataSet[lines[l].dataSetIndex] let x = 0, y = config.format.margin * pxMmRatio;
for (let l = 0; l < lines.length; l++) {
let setData = dataList[lines[l].dataIndex]
let ctx = pageList[0].el.getContext('2d', { willReadFrequently: true })
x = config.format.margin * pxMmRatio
x += lines[l].tabWidth
y = y + (lines[l].maxFontSize * pxMmRatio)
lines[l].y = y lines[l].y = y
if (lines[l].listIndex >= 0) {
let totalTabDistance = config.format.tabWidth * config.pageSetup.canvasMultiplier * (lines[l].tabCount + 1)
x += totalTabDistance;
let numberX = x - config.format.tabWidth * config.pageSetup.canvasMultiplier / 2
if (lines[l].blockStart) {
let style = {
...config.style,
...setData.style
}
ctx.save() ctx.save()
ctx.fillStyle = `${style.fontColor}`
// 10 * Math.PI/180 = 0.174 // this is to render the numbering and bullets etc
ctx.setTransform(1, 0.174, 0, 1, 0, 0) if (lines[l].dataType == 1) {
ctx.font = `${style.bold ? 'bold ' : ''} ${style.fontSize * config.pageSetup.fontMultiplier}px ${style.fontFamily}` ctx.save()
ctx.fillText(`${lines[l].listIndex + 1}.`, numberX, y) ctx.font = `${setData.style.fontSize * pxMmRatio}px ${setData.style.fontFamily}`
ctx.fillStyle = `${setData.style.fontColor}`
let label = getLabel(lines[l].dataIndex)
let labelWidth = ctx.measureText(label).width
labelWidth += pxMmRatio * 5
ctx.fillText(label, x - labelWidth, y)
ctx.restore() ctx.restore()
} }
}
lines[l].x = x
lines[l].canvasIndex = canvasIndex
for (let c = lines[l].charStartIndex; c <= lines[l].charEndIndex; c++) { for (let c = lines[l].charStartIndex; c <= lines[l].charEndIndex; c++) {
let char = setData?.plainContent[c] let char = setData?.plainContent[c]
if (char) { if (char) {
let style = setData.formatedText[c] let style = setData.formatedText[c]
ctx.save() ctx.save()
ctx.font = `${style?.bold ? 'bold ' : ''}${style?.italic ? 'italic ' : ''} ${style.fontSize * config.pageSetup.fontMultiplier}px ${style.fontFamily}` ctx.font = `${style?.bold ? 'bold ' : ''}${style?.italic ? 'italic ' : ''} ${style.fontSize * pxMmRatio}px ${style.fontFamily}`
ctx.fillStyle = `${style?.fontColor}` ctx.fillStyle = `${style?.fontColor}`
ctx.fillText(char, x, y) ctx.fillText(char, x, y)
setData.formatedText[c].x = x setData.formatedText[c].x = x
...@@ -474,413 +424,274 @@ var ADocEditor = function (customConfig) { ...@@ -474,413 +424,274 @@ var ADocEditor = function (customConfig) {
} }
} }
ctx.restore()
} }
return
} }
// to render caret function getLabel(dataIndex) {
function renderCaret() { let label = "x."
let ctx = canvasList[caretData.canvasIndex].el.getContext('2d', { willReadFrequently: true }) if (dataList[dataIndex].listStyle == 0) { // 1. a. i.
ctx.save() let tabCount = dataList[dataIndex].tabCount
tabCount = tabCount ? tabCount : 0
if (tabCount % 3 == 0) label = `${dataList[dataIndex].listItemNumber}.`
else if (tabCount % 3 == 1) label = `${convertToLetter(dataList[dataIndex].listItemNumber)}.`
else label = `${convertToRoman(dataList[dataIndex].listItemNumber).toLowerCase()}.`
}
else if (dataList[dataIndex].listStyle == 1) { // >
label = `>`
}
let activeDataIndex = dataSet.findIndex(item => item.id == caretData.activeData.id)
let activeLine = lines.find((item, i) => {
if (item.dataSetIndex == activeDataIndex) {
if (item.charEndIndex == -1) return true
if (item.charEndIndex >= caretData.index) return true
if (!(lines[i + 1]?.dataSetIndex == activeDataIndex)) return true
else false
} else return false
}) function convertToLetter(num) {
num = (num-1).toString(26)
let label = ""
for(let i=0;i<num.length;i++) label+= letters[num[i]]
// handle left right on multiple pages return label
if (activeLine) {
if (!isModule && option?.scrollIntoView) canvasList[activeLine.canvasIndex].el.focus()
caretData.canvasIndex = activeLine.canvasIndex
ctx.restore()
ctx = canvasList[activeLine.canvasIndex].el.getContext('2d', { willReadFrequently: true })
ctx.save()
} }
function convertToRoman(num) {
let characterData = dataSet[activeDataIndex].formatedText[caretData.index] let result = '';
let divisor = 1000;
let rectX = characterData?.x, for (let i = 3; i >= 0; i--) {
rectY = activeLine.y - (activeLine.maxFontSize * config.pageSetup.fontMultiplier), const digit = Math.floor(num / divisor);
rectWidth = 2, num %= divisor;
rectHeight = 5 * (activeLine.maxFontSize * config.pageSetup.fontMultiplier) / 4; divisor /= 10;
if (!characterData && dataSet[activeDataIndex].formatedText?.[caretData.index - 1]) {
rectX = dataSet[activeDataIndex].formatedText?.[caretData.index - 1]?.x + dataSet[activeDataIndex].formatedText?.[caretData.index - 1]?.width if (digit > 0) {
if (digit === 9) {
result += romanNumerals[i][3];
} else if (digit >= 5) {
result += romanNumerals[i][2];
result += romanNumerals[i][0].repeat(digit - 5);
} else if (digit === 4) {
result += romanNumerals[i][1];
} else {
result += romanNumerals[i][0].repeat(digit);
} }
if (activeLine.charEndIndex == caretData.index) {
rectX = dataSet[activeDataIndex].formatedText?.[caretData.index - 1]?.x + dataSet[activeDataIndex].formatedText?.[caretData.index - 1]?.width
} }
if (!(rectX > 0 || rectX == 0)) { rectX = activeLine.x }
const imageData = ctx.getImageData(rectX, rectY, rectWidth, rectHeight);
const data = imageData.data;
// Invert the color of the rectangular region
for (let i = 0; i < data.length; i += 4) {
data[i] = 255 - data[i]; // Red
data[i + 1] = 255 - data[i + 1]; // Green
data[i + 2] = 255 - data[i + 2]; // Blue
// Alpha channel remains unchanged (data[i + 3])
ctx.putImageData(imageData, rectX, rectY);
} }
if (characterData) {
caretData.style = { ...characterData } return result;
} else {
caretData.style = { ...caretData.activeData.style }
} }
ctx.restore() return label
} }
function getCharacterWidth(canvasIndex, char, style) {
let canvas = canvasList[canvasIndex].el function getCharacterWidth(char, style) {
let canvas = pageList[0].el
let ctx = canvas.getContext('2d', { willReadFrequently: true }) let ctx = canvas.getContext('2d', { willReadFrequently: true })
ctx.save() ctx.save()
ctx.font = `${style?.bold ? 'bold ' : ''}${style?.italic ? 'italic ' : ''} ${style.fontSize * config.pageSetup.fontMultiplier}px ${style.fontFamily}` ctx.font = `${style?.bold ? 'bold ' : ''}${style?.italic ? 'italic ' : ''} ${style.fontSize * pxMmRatio}px ${style.fontFamily}`
ctx.fillStyle = `${style?.fontColor}` ctx.fillStyle = `${style?.fontColor}`
let width = ctx.measureText(char).width let width = ctx.measureText(char).width
ctx.restore() ctx.restore()
return width return width
} }
function createNewCanvas() {
function createNewPage() {
let canvas; let canvas;
if (isModule) { if (isModule) {
const { createCanvas } = require('canvas') const { createCanvas } = require('canvas')
canvas = createCanvas(config.pageSetup.canvasWidth, config.pageSetup.canvasHeight) canvas = createCanvas(config.pageSetup.canvasWidth, config.pageSetup.canvasHeight)
} else { } else {
canvas = document.createElement('canvas') canvas = document.createElement('canvas')
let ctx = canvas.getContext('2d', { willReadFrequently: true })
canvas.setAttribute('class', 'page') canvas.setAttribute('class', 'page')
canvas.width = config.pageSetup.canvasWidth canvas.width = config.pageSetup.canvasWidth
canvas.height = config.pageSetup.canvasHeight canvas.height = config.pageSetup.canvasHeight
canvas.setAttribute("tabIndex", -1)
canvas.setAttribute('tabIndex', '-1')
canvas.width = config.pageSetup.pxWidth
canvas.height = config.pageSetup.pxHeight
canvas.style.width = `${config.pageSetup.uiWidth}mm`
let ctx = canvas.getContext('2d', { willReadFrequently: true })
ctx.fillStyle = config.format.background ctx.fillStyle = config.format.background
ctx.fillRect(0, 0, canvas.width, canvas.height) ctx.fillRect(0, 0, canvas.width, canvas.height)
canvas.addEventListener('keydown', keydownHandler) canvas.addEventListener('keydown', keydownHandler)
canvas.addEventListener('mousedown', mousedownHandler) canvas.addEventListener('mousedown', mousedownHandler)
canvas.addEventListener("focus", onFocusHandler) canvas.addEventListener("focus", onFocusHandler)
canvas.addEventListener("blur", onBlurHandler) canvas.addEventListener("blur", onBlurHandler)
canvas.setAttribute("tabIndex", -1)
scrollingComponent.append(canvas)
}
return canvas
}
function onFocusHandler(e) {
focusedCanvas = e.target
caretData.blink = false
reRenderPages(dataSet)
} }
function onBlurHandler(e) { return canvas
lastFocusCanvas = e.target
focusedCanvas = null
caretData.blink = false
reRenderPages(dataSet, { scrollIntoView: false })
clearInterval(caretData.interval)
} }
function keydownHandler(e) { function keydownHandler(e) {
caretData.blink = false if (e.altKey) return
else if (e.key == 'Tab') {
if (e.shiftKey && (e.ctrlKey || e.metaKey)) { e.preventDefault()
return manageIndentation(e.shiftKey ? -1 : 1)
} else if ([']', '['].includes(e.key) && e.ctrlKey) {
manageIndentation(e.key == '[' ? -1 : 1)
} }
else if (e.keyCode == 16) { // only shift key else if (e.key == 'Backspace') {
return if (caretData.index == 0) {
let activeDataIndex = dataList.findIndex(item => item.id == caretData.activeData.id)
if (caretData.activeData.tabCount) {
if (caretData.activeData.type==1){
caretData.activeData.type = 0
}else{
caretData.activeData.tabCount--
} }
// ctr+shift combination
else if (e.ctrlKey || e.metaKey) { // only ctrl or meta key
if (e.keyCode == 8) { // ctrl+backspace: delete the entrite word
let separatedSentence = caretData.activeData.plainContent.slice(0, caretData.index)
let indexOfpreviousBlankChar = separatedSentence.search(/[^a-zA-Z0-9](?=[a-zA-Z0-9]*$)/)
if (indexOfpreviousBlankChar <= 0) caretData.activeData.plainContent = ""
else caretData.activeData.plainContent = caretData.activeData.plainContent.slice(0, indexOfpreviousBlankChar) + caretData.activeData.plainContent.slice(caretData.index)
caretData.index = indexOfpreviousBlankChar <= 0 ? 0 : indexOfpreviousBlankChar
} }
else if (activeDataIndex > 0) {
if (caretData.activeData.type == 0) {
if (!caretData.activeData.plainContent.length) { dataList.splice(activeDataIndex, 1) }
caretData.activeData = dataList[activeDataIndex - 1]
caretData.index = caretData.activeData.plainContent.length
} else if (caretData.activeData.type == 1) {
caretData.activeData.type = 0
} }
else if (e.keyCode == 9) {// tab input // no need to add case for casesensitivity
e.preventDefault()
} }
else if (e.keyCode == 13) { // Enter Key } else {
if (caretData) { caretData.activeData.plainContent = caretData.activeData.plainContent.slice(0, caretData.index - 1) + caretData.activeData.plainContent.slice(caretData.index)
if (caretData.activeData.type == 0 || caretData.activeData.type == 1) { // for plain text // just go to next line caretData.activeData.formatedText.splice(caretData.index - 1, 1)
let style = caretData?.activeData?.formatedText?.[caretData.activeData.formatedText.length - 1] --caretData.index;
if (!style) style = caretData?.activeData?.style }
if (!style) style = config.style }
else if (e.key == 'Enter') {
let newLineData = { let dataObj = {
id: ++counter, id: ++counter,
type: caretData.activeData.type, type: caretData.activeData.type,
formatedText: [],
plainContent: "", plainContent: "",
style: JSON.parse(JSON.stringify(style)) listStyle: caretData.activeData.listStyle,
tabCount: caretData.activeData.tabCount,
style: { ...caretData.activeData.style }
} }
if (typeof caretData.activeData.tabCount == 'number') newLineData.tabCount = caretData.activeData.tabCount if (e.ctrlKey || e.metaKey) dataObj.newPage = true
newLineData.plainContent = caretData.activeData.plainContent.slice(caretData.index)
caretData.activeData.plainContent = caretData.activeData.plainContent.slice(0, caretData.index)
let currentDataIndex = dataSet.findIndex(item => item.id == caretData.activeData.id)
dataSet.splice(currentDataIndex + 1, 0, newLineData)
caretData.activeData = dataSet[currentDataIndex + 1]
caretData.y += style.fontSize
caretData.index = 0 caretData.index = 0
caretData.activeData = dataObj
} if (dataObj.type == 0) dataObj.tabCount = 0
dataList.push(dataObj)
} }
} else if (e.key.length == 1 && !e.ctrlKey && !e.metaKey) { // displayable text
else if (e.key.length == 1 || e.keyCode == 32) { // these are printable characters key // ***** DISPLAYABLE TEXT **** //
e.preventDefault() e.preventDefault()
caretData.style.fontFamily = fontFamily.innerText
caretData.style.fontSize = Number(fontSize.value)
caretData.style.fontColor = fontColor.value
caretData.style.bold = fontBold.classList.contains('selected')
caretData.style.italic = fontItalic.classList.contains('selected')
caretData.activeData.formatedText.splice(caretData.index, 0, { ...caretData.style })
caretData.activeData.plainContent = caretData.activeData.plainContent.slice(0, caretData.index) + e.key + caretData.activeData.plainContent.slice(caretData.index) caretData.activeData.plainContent = caretData.activeData.plainContent.slice(0, caretData.index) + e.key + caretData.activeData.plainContent.slice(caretData.index)
caretData.activeData.formatedText.splice(caretData.index, 0, caretData.style)
++caretData.index ++caretData.index
} }
else if (e.keyCode == 8) { // backspace else if (e.key == 'ArrowLeft') {
if (caretData.index <= 0) { if (caretData.index) {
let currentIndex = dataSet.findIndex(item => item.id == caretData.activeData.id) --caretData.index
if (currentIndex > 0) {
caretData.activeData = dataSet[currentIndex - 1]
caretData.index = dataSet[currentIndex - 1].plainContent.length
if (dataSet[currentIndex].plainContent.length != 0) {
caretData.activeData.plainContent += dataSet[currentIndex].plainContent
caretData.activeData.formatedText.push(...dataSet[currentIndex].formatedText)
console.log()
}
dataSet.splice(currentIndex, 1)
}
} else { } else {
caretData.activeData.plainContent = caretData.activeData.plainContent.slice(0, caretData.index - 1) + caretData.activeData.plainContent.slice(caretData.index) let dataIndex = dataList.findIndex(item => item.id == caretData.activeData.id)
caretData.index = caretData.index - 1 if (dataIndex > 0) {
caretData.activeData.formatedText.splice( caretData.index, 1 ) caretData.activeData = dataList[dataIndex - 1]
caretData.index = caretData.activeData.plainContent.length
} }
} }
else if (e.keyCode == 37) { // left key
e.preventDefault()
if (caretData.index <= 0) {
let currentIndex = dataSet.findIndex(item => item.id == caretData.activeData.id)
let previousData = dataSet[currentIndex - 1]
if (previousData) {
caretData.activeData = previousData
caretData.index = previousData.plainContent.length
}
caretData.canvasIndex = canvasList.findIndex(item => item.el == e.target)
} else {
caretData.index = (caretData.index <= 0) ? 0 : caretData.index - 1
} }
else if (e.key == 'ArrowRight') {
reRenderPages(dataSet, { onlyCursor: true }) if (caretData.index < caretData.activeData.plainContent.length) {
return ++caretData.index
}
else if (e.keyCode == 38) { // up key
e.preventDefault()
}
else if (e.keyCode == 39) { // right key
e.preventDefault()
if (caretData.index <= caretData.activeData.plainContent.length - 1) {
caretData.index = (caretData.index >= caretData.activeData.plainContent.length) ? caretData.activeData.plainContent.length : caretData.index + 1
} else { } else {
let currentIndex = dataSet.findIndex(item => item.id == caretData.activeData.id) let dataIndex = dataList.findIndex(item => item.id == caretData.activeData.id)
let nextData = dataSet[currentIndex + 1] if (dataList[dataIndex + 1]) {
if (nextData) { caretData.activeData = dataList[dataIndex + 1]
caretData.index = 0 caretData.index = 0
caretData.activeData = nextData
} }
} }
caretData.canvasIndex = canvasList.findIndex(item => item.el == e.target)
reRenderPages(dataSet, { onlyCursor: true })
return
} }
else if (e.keyCode == 40) { // down key else if (e.key == 'ArrowUp') {
e.preventDefault() console.log('Up')
}
else if (e.key == 'ArrowDown') {
console.log('Down')
} }
else { } // unhandled cases
reRenderPages(dataSet) function manageIndentation(value) {
clearInterval(caretData.interval) caretData.activeData.tabCount += value
if (caretData.activeData.tabCount < 0) caretData.activeData.tabCount = 0
else if (caretData.activeData.tabCount > 5) caretData.activeData.tabCount = 5
return true
} }
function setCaretPosition(e) { caretData.blink = false
let canvasIndex = canvasList.findIndex(item => item.el == e.target) caretData.previousCaret = null
let rect = e.target.getBoundingClientRect() reRenderCanvas()
let position = {
x: (e.offsetX / rect.width) * config.pageSetup.canvasWidth,
y: (e.offsetY / rect.height) * config.pageSetup.canvasHeight,
} }
let closestLine = lines.find((item, i) => { function placeCaret(cursor = { x: 0, y: 0 }) {
let thisLine = (item.y + item.maxFontSize / 4) > position.y && canvasIndex == item.canvasIndex
if (thisLine) return true
else if (!(lines[i + 1]) || (lines[i + 1]?.canvasIndex > canvasIndex)) return true
return false
})
if (closestLine) {
caretData.activeData = dataSet[closestLine.dataSetIndex]
let charIndex = closestLine.charStartIndex
let found = false let found = false
for (let i = charIndex; (i <= closestLine.charEndIndex && caretData.activeData.formatedText[i].y == closestLine.y); i++) { for (let l = 0; l < lines.length; l++) {
let formatedText = caretData.activeData.formatedText[i] if ((cursor.y <= lines[l].y) && (cursor.y >= (lines[l].maxFontSize * pxMmRatio))) {
if (position.x < (formatedText.x + formatedText.width)) { let dataSet = dataList[lines[l].dataIndex]
charIndex = i for (let c = lines[l].charStartIndex; c <= lines[l].charEndIndex; c++) {
if ((cursor.x >= dataSet.formatedText[c].x) && (cursor.x <= (dataSet.formatedText[c].width + dataSet.formatedText[c].x))) {
caretData.activeData = dataSet
if (cursor.x <= dataSet.formatedText[c].x + dataSet.formatedText[c].width / 2) {
caretData.index = c
} else {
caretData.index = c + 1
}
found = true found = true
break; caretData.blink = false
renderCaret(true)
return
}
} }
charIndex = i + 1 if (found) break; // break only when the caret index is determined
} }
if (/\s+/.test(caretData.activeData.plainContent[charIndex]) && caretData.activeData.plainContent[charIndex + 1] && charIndex == closestLine.charStartIndex) {
charIndex++
} }
caretData.index = charIndex if (!found) {
console.log('no char found')
} }
reRenderPages(dataSet)
} }
function mousedownHandler(e) { function mousedownHandler(e) {
e.target.focus({ preventScroll: true }); if (focussedPage) {
setCaretPosition(e) const rect = e.target.getBoundingClientRect(); // Get the position of the canvas
} const pxX = (e.clientX - rect.left) * config.pageSetup.pxWidth / rect.width;
renderInProgress = false const pxY = (e.clientY - rect.top) * config.pageSetup.pxHeight / rect.height;
placeCaret({ x: pxX, y: pxY })
let style = caretData.activeData.formatedText[caretData.index]
if (style) caretData.style = { ...caretData.style,
fontSize: style.fontSize,
} }
function removeStyles(dataList) { shadow.querySelector('[adc="font-size-input"]').value = caretData.style.fontSize
for (let i = 0; i < dataList.length; i++) {
dataList[i].formatedText = []
for (let j = 0; j < dataList[i].plainContent.length; j++) {
dataList[i].formatedText.push(JSON.parse(JSON.stringify(config.style)))
} }
} }
function onFocusHandler(e) {
focussedPage = e.target
lastFocussedPage = e.target
reRenderCanvas()
} }
function onBlurHandler(e) {
async function generatePDF(file) { caretData.blink = true
focussedPage = null
showLoader()
let embededFonts = {}
// Create a new PDFDocument
const pdfDoc = await PDFDocument.create()
function embedFont(name) {
return new Promise((res, rej) => {
let fontObj = fontList.find(item => item.name == name)
let url = fontObj.paths.find(item => item.slice(-4) == '.ttf')
if (!url) fontObj.paths[0]
fetch(url)
.then(async (data) => {
let arrayBuffer = await data.arrayBuffer()
embededFonts[name] = await pdfDoc.embedFont(arrayBuffer)
res(embededFonts[name])
}).catch((err) => {
rej(err)
})
})
}
// register font-kit
pdfDoc.registerFontkit(fontkit)
// Add a blank page to the document
var page = pdfDoc.addPage([config.pageSetup.width, config.pageSetup.height])
let canvasIndex = 0
let x = 0
let y = (config.format.margin * config.pageSetup.canvasMultiplier)
let maxVericalWidth = (config.pageSetup.canvasHeight - config.format.margin * config.pageSetup.canvasMultiplier * 2)
for (let l = 0; l < lines.length; l++) {
x = (config.format.margin * config.pageSetup.canvasMultiplier);
y += (lines[l].maxFontSize * config.pageSetup.fontMultiplier)
if (lines[l].blockStart && l != 0) y += lines[l].maxFontSize
if ((maxVericalWidth + lines[l].maxFontSize) < (y - lines[l].maxFontSize)) {
canvasIndex++
page = pdfDoc.addPage([config.pageSetup.width, config.pageSetup.height])
y = lines[l].maxFontSize + (config.format.margin * config.pageSetup.canvasMultiplier)
} }
let setData = dataSet[lines[l].dataSetIndex]
lines[l].y = y
if (lines[l].listIndex >= 0) {
let totalTabDistance = config.format.tabWidth * config.pageSetup.canvasMultiplier * (lines[l].tabCount + 1)
x += totalTabDistance;
let numberX = x - config.format.tabWidth * config.pageSetup.canvasMultiplier / 2
if (lines[l].blockStart) {
let style = {
...config.style,
...setData.style
}
if (!embededFonts[style.fontFamily]) await embedFont(style.fontFamily)
page.drawText(`${lines[l].listIndex + 1}.`, {
x: numberX / config.pageSetup.canvasMultiplier,
y: (config.pageSetup.canvasHeight - y) / config.pageSetup.canvasMultiplier,
size: (style.fontSize * config.pageSetup.fontMultiplier) / config.pageSetup.canvasMultiplier,
font: embededFonts[style.fontFamily],
color: rgb( ...getRgbArrayFromHex( style.fontColor ) ),
})
} }
} function changeListStyle(){
lines[l].x = x let activeDataIndex = dataList.findIndex( item => item.id == caretData.activeData.id )
lines[l].canvasIndex = canvasIndex let d = activeDataIndex-1
for (let c = lines[l].charStartIndex; c <= lines[l].charEndIndex; c++) { while( dataList?.[d]?.type==1){
let char = setData?.plainContent[c] dataList[d].listStyle = caretData.activeData.listStyle
if (char) { --d
let style = setData.formatedText[c]
if (!embededFonts[style.fontFamily]) await embedFont(style.fontFamily)
page.drawText(dataSet[lines[l].dataSetIndex].plainContent[c], {
x: style.x / config.pageSetup.canvasMultiplier,
y: (config.pageSetup.canvasHeight - style.y) / config.pageSetup.canvasMultiplier,
size: (style.fontSize * config.pageSetup.fontMultiplier) / config.pageSetup.canvasMultiplier,
font: embededFonts[style.fontFamily],
color: rgb( ...getRgbArrayFromHex( style.fontColor ) ),
ySkew: { type: 'radians', angle: style.italic?0.174:0 },
})
setData.formatedText[c].x = x
setData.formatedText[c].y = y
if (setData.formatedText[c]?.width) {
x += setData.formatedText[c]?.width
}
} }
d = activeDataIndex+1
while(dataList?.[d]?.type==1){
dataList[d].listStyle = caretData.activeData.listStyle
++d
} }
} }
const pdfBytes = await pdfDoc.save()
const blob = new Blob([pdfBytes], { type: "application/pdf" });
const downloadLink = document.createElement("a");
document.body.appendChild(downloadLink);
downloadLink.href = URL.createObjectURL(blob);
downloadLink.download = file; // Set desired filename
downloadLink.style.display = "none"; // Keep it hidden
downloadLink.click()
document.body.removeChild(downloadLink)
hideLoader()
}
function getRgbArrayFromHex(hexString){
let rgbArr= [ 0,0,0 ]
if (hexString.length==4 || hexString.length==5){
rgbArr = [ parseInt(hexString[1], 16)/15, parseInt(hexString[2], 16)/15, parseInt(hexString[3], 16)/15 ]
}else if (hexString.length==7 || hexString.length==9){
rgbArr = [ parseInt(hexString.slice(1,3), 16)/255, parseInt(hexString.slice(3,5), 16)/255, parseInt(hexString.slice(5,7), 16)/255 ]
}
return rgbArr
}
function addFonts(paths, name) { function addFonts(paths, name) {
if (typeof paths == 'string') paths = [paths] if (typeof paths == 'string') paths = [paths]
...@@ -909,86 +720,14 @@ var ADocEditor = function (customConfig) { ...@@ -909,86 +720,14 @@ var ADocEditor = function (customConfig) {
} }
}
// // these are methods related to the toolbar items
function changeFontFamily(e) {
if (e) {
caretData.style.fontFamily = e.target.innerText
caretData.styleModified = false
focusLastCanvas()
if (!caretData.activeData.plainContent.length) {
caretData.activeData.style.fontFamily = e.target.innerText
}
} else {
let labels = container.querySelectorAll(`[ade-target="#${fontFamilyDropdown.id}"]`)
labels.forEach(item => item.innerText = caretData.style.fontFamily)
}
}
function changeFontSize(change){
fontSize.value = change
caretData.style.fontSize = change
focusLastCanvas()
if (!caretData.activeData.plainContent.length) {
caretData.activeData.style.fontSize = change
}
}
function changeFontColor(e){
fontColorLabel.style.backgroundColor = e.target.value
caretData.style.fontColor = e.target.value
focusLastCanvas()
if (!caretData.activeData.plainContent.length) {
caretData.activeData.style.fontColor = e.target.value
}
}
function toggleBold(e){
let isBold = fontBold.classList.contains('selected')
caretData.style.bold = !isBold
if (!isBold) fontBold.classList.add('selected')
else fontBold.classList.remove('selected')
focusLastCanvas()
if (!caretData.activeData.plainContent.length) {
caretData.activeData.style.bold = !isBold
}
}
function toggleItalic(e){
let isItalic = fontItalic.classList.contains('selected')
caretData.style.italic = !isItalic
if (!isItalic) fontItalic.classList.add('selected')
else fontItalic.classList.remove('selected')
focusLastCanvas()
if (!caretData.activeData.plainContent.length) {
caretData.activeData.style.italic = !isItalic
}
}
function focusLastCanvas(){
if (!lastFocusCanvas) lastFocusCanvas = canvasList[0].el
if (lastFocusCanvas) {
lastFocusCanvas.focus({ preventScroll: true })
}
}
function reRenderFontDropdown() { function reRenderFontDropdown() {
let fontFamilyDropdown = headerToolbar.find(item => item.getAttribute('adc') == 'font-select')
if (fontFamilyDropdown) { if (fontFamilyDropdown) {
fontFamilyDropdown.innerHTML = "" fontFamilyDropdown.innerHTML = ""
fontList.forEach((font, i) => { fontList.forEach((font, i) => {
let optionTag = document.createElement('div') let optionTag = document.createElement('option')
optionTag.setAttribute('ade-type', 'option')
optionTag.setAttribute('class', 'dropdown-option')
optionTag.setAttribute('value', font.name) optionTag.setAttribute('value', font.name)
optionTag.style.fontFamily = font.name optionTag.style.fontFamily = font.name
optionTag.value = font.name
optionTag.innerText = font.name optionTag.innerText = font.name
fontFamilyDropdown.append(optionTag) fontFamilyDropdown.append(optionTag)
}) })
...@@ -996,92 +735,117 @@ var ADocEditor = function (customConfig) { ...@@ -996,92 +735,117 @@ var ADocEditor = function (customConfig) {
changeFontFamily() changeFontFamily()
} }
function changeFontFamily() {
function bindGlobalEvents() { let fontFamilyDropdown = headerToolbar.find(item => item.getAttribute('adc') == 'font-select')
if (fontFamilyDropdown) fontFamilyDropdown.value = caretData.style.fontFamily
let options = container.querySelectorAll('[ade-target]')
options.forEach(option => {
let target = container.querySelector(option.getAttribute('ade-target'))
option.innerText = target?.children?.[0]?.innerText ? target?.children?.[0]?.innerText : ''
})
fontSize.value = caretData.style.fontSize
fontColor.value = caretData.style.fontColor
fontColorLabel.style.backgroundColor = caretData.style.fontColor
container.addEventListener('click', (e) => {
if (e.target.getAttribute('ade-type') == 'option') {
let parent = e.target.parentElement
let labelList = container.querySelectorAll(`[ade-target="#${parent.id}"]`)
labelList.forEach(item => {
item.innerText = e.target.innerText
})
closeAllExcept()
} else {
let target = container.querySelector(e.target.getAttribute('ade-target'))
closeAllExcept(target)
} }
if (e.target.getAttribute('ade-action')=='font-size-minus'){
changeFontSize(Number(fontSize.value)-1)
} }
if (e.target.getAttribute('ade-action')=='font-size-plus'){
changeFontSize(Number(fontSize.value)+1)
function onMouseWheelHandler(e) {
if (e.ctrlKey || e.metaKey) e.preventDefault()
} }
function closeAllExcept(except) {
let dropdownLists = container.querySelectorAll('[ade-type="dropdown"]') function addGlobalEvents(e) {
dropdownLists.forEach(item => { shadow.querySelector('.page-list').addEventListener('wheel', onMouseWheelHandler)
if (item == except) { shadow.addEventListener('mousedown', globalMouseDownHandler)
item.classList.toggle('show')
} }
function globalMouseDownHandler(e) {
var elem = e.target
var targetId = elem.getAttribute('adc-target')
if (elem.getAttribute('adc-type') == 'popover') { return }
while (!targetId && elem) {
elem = elem.parentNode
if (elem?.getAttribute?.('adc-type') == 'popover') return
targetId = elem?.getAttribute?.('adc-target')
}
let allPopovers = shadow.querySelectorAll('[adc-type="popover"]')
allPopovers.forEach(item => {
if (item.getAttribute('adc') == targetId) item.classList.toggle('show')
else item.classList.remove('show') else item.classList.remove('show')
}) })
} }
})
function renderCaret(toLoop) {
clearTimeout(caretData.timeout)
let ctx = pageList[0].el.getContext('2d', { willReadFrequently: true })
ctx.save()
if (caretData.previousCaret) {
ctx.putImageData(caretData.previousCaret.imageData, caretData.previousCaret.x, caretData.previousCaret.y);
caretData.previousCaret = null
} }
function showLoader(){ if (!caretData.blink) {
container.append(fullScreenLoadingOverlay) let dataIndex = dataList.findIndex(item => item.id == caretData.activeData.id)
fullScreenLoadingOverlay.addEventListener('mousedown', preventEvents, true) let lineObj = lines.find(item => item.dataIndex == dataIndex && caretData.index >= item.charStartIndex)
fullScreenLoadingOverlay.addEventListener('keydown', preventEvents, true) let x = (config.format.margin * pxMmRatio) + lineObj.tabWidth
} let y = (config.format.margin) * pxMmRatio
function hideLoader(){ let height = caretData.style.fontSize * pxMmRatio * 5 / 4
fullScreenLoadingOverlay.removeEventListener('mousedown', preventEvents, true) let width = height / 10
fullScreenLoadingOverlay.removeEventListener('keydown', preventEvents, true)
fullScreenLoadingOverlay.remove()
let charData = caretData.activeData.formatedText[caretData.index - 1]
if (lineObj) {
x = (charData?(charData.x+charData.width):x)
if(charData){
y = charData.y - (caretData.style.fontSize*pxMmRatio)
}else{
y = lineObj.y - (lineObj.maxFontSize*pxMmRatio)
} }
function preventEvents(event) {
event.preventDefault();
event.stopPropagation();
} }
function setupListItem(){
const imageData = ctx.getImageData(x, y, width, height);
const data = imageData.data;
caretData.previousCaret = { imageData: structuredClone(imageData), x, y }
// Invert the color of the rectangular region
for (let i = 0; i < data.length; i += 4) {
data[i] = 255 - data[i]; // Red
data[i + 1] = 255 - data[i + 1]; // Green
data[i + 2] = 255 - data[i + 2]; // Blue
// Alpha channel remains unchanged (data[i + 3])
} }
ctx.putImageData(imageData, x, y);
inititalize(customConfig) caretData.blink = true
function destory() { } else {
clearInterval(caretData.interval) caretData.blink = false
} }
function getContent() { ctx.restore()
return JSON.parse(JSON.stringify(dataSet)) if (toLoop) {
caretData.timeout = setTimeout(() => {
renderCaret(true)
}, caretData.timeoutDuration)
}
return
}
function focusOnPage() {
caretData.blink = false
if (!lastFocussedPage) lastFocussedPage = pageList[0].el
if (lastFocussedPage) {
const scrollTop = pageScrollingDiv.scrollTop
lastFocussedPage.focus()
pageScrollingDiv.scrollTop = scrollTop
}
} }
var returnObj = { var returnObj = {
destory, addFonts,
loadContent: function (data) { getConfiguration() { return JSON.parse(JSON.stringify(config)) },
dataSet = JSON.parse(JSON.stringify(data)) getPages() {
reRenderPages(dataSet) let pagesToReturn = JSON.parse(JSON.stringify(pageList))
caretData.activeData = null for (let i = 0; i < pagesToReturn.length; i++) pagesToReturn[i].canvas = pageList[i].canvas
caretData.activeData = dataSet[0] return pagesToReturn
caretData.index = 0
}, },
getContent, log() {
addFonts: addFonts, console.log('dataList', dataList)
generatePDF, console.log('lines', lines)
console.log('caretData', caretData)
},
focusOnPage,
} }
initialize()
return returnObj return returnObj
} }
......
.main {
position: relative;
display: flex;
width: 100%;
height: 100%;
overflow: hidden;
background-color: #858585;
flex-direction: column;
justify-content: flex-start;
}
.header {
position: relative;
display: block;
width: 100%;
background-color: red;
}
.toolbar {
position: relative;
width: 100%;
background: pink;
display: flex;
gap: 10px;
}
.toolbar .item {
position: relative;
background: #408640;
border: 1px solid gray;
border-radius: 3px;
padding-top: 2px;
padding-bottom: 2px;
padding-left: 4px;
padding-right: 4px;
margin: 4px;
cursor: pointer;
}
.popover{
position: absolute;
padding: 5px;
background-color: gray;
border: 1px solid purple;
opacity: 0;
pointer-events: none;
}
.popover.show{
opacity: 1;
pointer-events: auto;
z-index: 10;
}
.toolbar .item:hover {
background: #5fad5f;
}
[adc-type="popover"] {
pointer-events: none;
}
.small-input {
width: 50px;
}
.small-btn {
width: 25px;
}
.option {
border: 1px solid black;
padding: 5px;
margin: 5px;
border-radius: 4px;
}
.option:hover {
background-color: #f5c468;
}
.page-list {
position: relative;
display: block;
overflow-y: auto;
overflow-x: auto;
background: #858585;
gap: 20px;
align-items: center;
}
.page-list canvas {
background-color: #fff;
width: 210mm;
height: auto;
display: block;
position: relative;
margin: 20px;
}
.footer {
position: relative;
display: block;
width: 100%;
background-color: red;
}
\ No newline at end of file
let isModule = (typeof module != 'undefined') ? true : false
if (!isModule) console.log('Browser Environment')
var ADocEditor = function (customConfig) {
var container, counter = 0, shadow, pxMmRatio, canvasPxMmRatio, config, htmlStr, htmlTag, htmlObj = {}, fontList = [], headerToolbar = [];
var paperSizes = {
"A4": { mmWidth: 210, mmHeight: 297 },
}
const letters = { "0": "a","1": "b","2": "c","3": "d","4": "e","5": "f","6": "g","7": "h","8": "i","9": "j","a": "k","b": "l","c": "m","d": "n","e": "o","f": "p","g": "q","h": "r","i": "s","j": "t","k": "u","l": "v","m": "w","n": "x","o": "y","p": "z" }
const romanNumerals = [ ["I", "IV", "V", "IX"], ["X", "XL", "L", "XC"], ["C", "CD", "D", "CM"], ["M"] ];
var defaultConfig = {
pageSetup: { size: "A4" }, zoom: 1,
format: {
background: "#fff",
margin: 15, // mm
border: "",
tabWidth: 20, // mm
},
style: {
fontSize: 10, // this is in mm
fontFamily: 'Calibri',
bold: false,
italic: false,
fontColor: "#f01"
},
}
config = JSON.parse(JSON.stringify(defaultConfig))
var dataList = [
{
id: ++counter,
type: 0,
formatedText: [],
plainContent: "",
tabCount: 0,
style: { ...config.style },
listItemNumber: 0,
// newPage: false, // if this is true the data is send to new page
},
]
var caretData = {
activeData: dataList[0],
index: 0,
timeout: null,
timeoutDuration: 800,
blink: true,
pageIndex: 0,
caretSize: config.style.fontSize,
previousCaret: null,
style: { ...dataList[0].style, x: config.format.margin, y: config.format.margin + (3 * config.style.fontSize / 4) },
}
var pageList = []
var pageScrollingDiv = null
var focussedPage = null
var lastFocussedPage = null
var isRendering = false
function initialize() {
config = JSON.parse(JSON.stringify(defaultConfig))
container = customConfig.container
shadow = container.attachShadow({ mode: "open" })
htmlStr = /*html*/`
<div class="header">
<div class="toolbar">
<select class="item" adc="font-select"></select>
<div class="item" adc="list-handler" adc-target="list-popover">
<span>L</span>
<div class="popover" adc="list-popover" adc-type="popover">
<div class="option" adc-toggle="listing-option" value="1">•&nbsp;&nbsp;Bullets&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</div>
<div class="option" adc-toggle="listing-option" value="0">1. Numbers</div>
</div>
</div>
<div class="item">
<button adc="font-size-change" class="small-btn" value="-1">-</button>
<input type="number" adc="font-size-input" class="small-input">
<button adc="font-size-change" class="small-btn" value="+1">+</button>
</div>
</div>
</div>
<div class="page-list" adc="page-list"></div>
<div class="footer">
<div class="toolbar">
<select class="item" title="Select zoom" adc="zoom">
<option value="" disabled>Select Zoom</option>
<option value="0.1">10%</option>
<option value="0.2">20%</option>
<option value="0.3">30%</option>
<option value="0.4">40%</option>
<option value="0.5">50%</option>
<option value="0.6">60%</option>
<option value="0.7">70%</option>
<option value="0.8">80%</option>
<option value="0.9">90%</option>
<option value="1" selected>100%</option>
<option value="1.2">120%</option>
<option value="1.5">150%</option>
<option value="1.8">180%</option>
<option value="2">200%</option>
</select>
<span class="item">Words : 0</span>
<span class="item">Sentences : 0</span>
<span class="item">Pages : 1</span>
</div>
</div>
`;
let styleSheets = customConfig?.styleSheet?.length ? customConfig.styleSheet : []
styleSheets.splice(0, 0, "/assets/a-doc-editor2.css")
for (let i = 0; i < styleSheets.length; i++) {
let link = document.createElement('link')
link.setAttribute('rel', 'stylesheet');
link.setAttribute('href', styleSheets[i]);
shadow.append(link)
}
htmlTag = document.createElement('div')
htmlTag.setAttribute('class', 'main')
htmlTag.innerHTML = htmlStr
shadow.append(htmlTag)
htmlObj = {
header: shadow.querySelector('.header'),
pageList: shadow.querySelector('page-list')
}
pageScrollingDiv = shadow.querySelector('[adc="page-list"]')
!(function zoomHandles() {
let zoomSelect = shadow.querySelector('[adc="zoom"]')
zoomSelect.addEventListener('change', (e) => {
config.zoom = Number(e.target.value)
reConfigure(config)
})
})()
!(function fontSelectHandle() {
let fontSelect = shadow.querySelector('[adc="font-select"]')
headerToolbar.push(fontSelect)
fontSelect.addEventListener('change', (e) => {
caretData.style = {
...caretData.style,
fontFamily: e.target.value,
}
focusOnPage()
})
addFonts(["./assets/fonts/Afacad-VariableFont_wght.woff2", "./assets/fonts/Afacad-VariableFont_wght.ttf"], 'Afacad')
addFonts(["./assets/fonts/ArchitectsDaughter-Regular.woff2", "./assets/fonts/ArchitectsDaughter-Regular.ttf"], 'Architects Daughter')
addFonts(["./assets/fonts/Assistant-VariableFont_wght.woff2", "./assets/fonts/Assistant-VariableFont_wght.ttf"], 'Assistant')
addFonts(["./assets/fonts/Bitter-VariableFont_wght.woff2", "./assets/fonts/Bitter-VariableFont_wght.ttf"], 'Bitter')
addFonts(["./assets/fonts/calibri-regular.woff2", "./assets/fonts/calibri-regular.ttf"], 'Calibri')
})()
!(function handleList() {
let listItems = shadow.querySelectorAll('[adc-toggle="listing-option"]')
let popover = shadow.querySelector('[adc-type="popover"]')
for(let i=0; i<listItems.length; i++){
listItems[i].addEventListener('click', (e) => {
let value = Number(e.target.getAttribute('value'))
value = value?value:0
if (caretData.activeData) {
caretData.activeData.type = (caretData.activeData.type == 1 && caretData.activeData.listStyle==value) ? 0 : 1
if (caretData.activeData.type==1){
caretData.activeData.listStyle = value
}
changeListStyle()
caretData.blink = false
reRenderCanvas()
popover.classList.remove('show')
focusOnPage()
}
})
}
})()
!(function fontSizeHandler(){
let fontSizeInput = shadow.querySelector('[adc="font-size-input"]')
fontSizeInput.value = caretData.style.fontSize
let fontSizeChangers = shadow.querySelectorAll('[adc="font-size-change"]')
fontSizeChangers.forEach( btn => {
btn.addEventListener( 'click',(e)=>{
let change = Number(e.target.getAttribute('value'))
change = change?change:0
change = caretData.style.fontSize + change
change = change?change:config.style.fontSize
caretData.style.fontSize = change
fontSizeInput.value = change
focusOnPage()
} )
} )
function changeFontEvent(e){
let value = Number( e.target.value )
value = value?value:config.style.fontSize
caretData.style.fontSize = value
if (e?.key == 'Enter') focusOnPage()
}
fontSizeInput.addEventListener('keydown', changeFontEvent)
fontSizeInput.addEventListener('input', changeFontEvent)
})()
reConfigure(customConfig)
reRenderCanvas()
addGlobalEvents()
}
function reConfigure(newConfig) {
if (newConfig?.size && paperSizes[newConfig?.size]) { config.pageSetup = { ...paperSizes[newConfig.size], size: newConfig.size } }
else if (newConfig?.width && newConfig?.height) {
config.pageSetup = { size: "Custom", mmWidth: newConfig.width, mmHeight: newConfig.height }
}
else { config.pageSetup = { size: "A4", ...paperSizes['A4'] } }
if (typeof window == 'object') {
config.pageSetup.multipler = config.pageSetup.mmHeight / config.pageSetup.mmWidth
config.pageSetup.pxWidth = 2480
config.pageSetup.pxHeight = Math.ceil(config.pageSetup.pxWidth * config.pageSetup.multipler)
} else {
config.pageSetup.pxHeight = config.pageSetup.mmHeight
config.pageSetup.pxWidth = config.pageSetup.mmWidth
}
if (config.zoom) {
config.pageSetup.uiWidth = config.pageSetup.mmWidth * config.zoom
config.pageSetup.uiHeight = config.pageSetup.mmHeight * config.zoom
}
for (let p = 0; p < pageList.length; p++) {
pageList[p].el.style.width = `${config.pageSetup.uiWidth}mm`
}
pxMmRatio = config.pageSetup.pxWidth / config.pageSetup.mmWidth;
return config
}
function reRenderCanvas() {
if (isRendering) return
let pageIndex = 0
if (!pageList.length) {
pageList[pageIndex] = { id: ++counter, el: createNewPage(), dataIndex: 0 }
shadow.querySelector('.page-list').append(pageList[pageIndex].el)
}
// to clear the canvases
for (let i = 0; i < pageList.length; i++) {
let ctx = pageList[i].el.getContext('2d', { willReadFrequently: true })
ctx.save()
ctx.clearRect(0, 0, pageList[i].el.width, pageList[i].el.height); // clears the canvas
ctx.fillStyle = config.format.background
ctx.fillRect(0, 0, pageList[i].el.width, pageList[i].el.height)
ctx.restore()
}
lines = []
calculateTextSizeAndPosition()
renderLines()
caretData.previousCaret = null
renderCaret(true)
function calculateTextSizeAndPosition() {
let d = 0, c = 0;
function getLineObj() {
let newLineObj = {
...config.style,
x: 0, // this is the starting point x; it will change based on the tabNumber
y: 0, // this is the starting y coordinate; it will change based on the max font size
plainContent: "",
dataIndex: d,
charStartIndex: 0, // index from where to check
charEndIndex: 0, // index till where to check// not including this index.
listItemNumber: 0,
}
newLineObj.maxFontSize = newLineObj.fontSize
return newLineObj
}
for (d = 0; d < dataList.length; d++) {
let lineObj = getLineObj()
let dataBlock = dataList[d]
let canvas = pageList[pageIndex].el
let ctx = canvas.getContext('2d', { willReadFrequently: true })
ctx.save()
// to calculate the lines
let tempLineWidth = 0;
let maxLineWidth = config.pageSetup.pxWidth - (config.format.margin * pxMmRatio * 2)
let tabWidth = ((dataBlock.type == 1 ? 1 : 0) + dataBlock.tabCount) * config.format.tabWidth * pxMmRatio
lineObj.tabWidth = tabWidth
maxLineWidth -= tabWidth
lineObj.dataType = dataBlock.type
let wordEndIndex = 0; // this stores the index of the word which can fit in the line;
let tempWordWidth = 0
if (dataBlock.type == 1) {
let previousBlock = dataList[d - 1]
if (!previousBlock || previousBlock?.type != 1) dataBlock.listItemNumber = 1
else if (previousBlock.tabCount == dataBlock.tabCount) {
dataBlock.listItemNumber = previousBlock.listItemNumber + 1
} else {
dataBlock.listItemNumber = 1
let olderBlockIndex = d - 1
while (dataList?.[olderBlockIndex]?.type == 1 && dataList?.[olderBlockIndex]?.tabCount >= dataBlock.tabCount) {
if (dataList?.[olderBlockIndex]?.tabCount == dataBlock.tabCount) {
dataBlock.listItemNumber = dataList?.[olderBlockIndex].listItemNumber + 1
break;
}
--olderBlockIndex;
}
}
}
for (c = 0; c < dataBlock.plainContent.length; c++) {
let style = dataBlock?.formatedText?.[c]
lineObj.maxFontSize = (lineObj.maxFontSize<dataBlock?.formatedText?.[c].fontSize)?dataBlock?.formatedText?.[c].fontSize:lineObj.maxFontSize
if (/\s/.test(dataBlock.plainContent[c])) {
// condition to check if a blank character is found.
wordEndIndex = c
lineObj.charEndIndex = c
tempWordWidth = 0
}
let charWidth = getCharacterWidth(dataBlock.plainContent[c], style)
dataBlock.formatedText[c] = {
...config.style,
...style,
width: charWidth
}
// // if with the current char the string could not fit on one line
if (tempLineWidth + charWidth > maxLineWidth) {
// cannot add this// new line should be added//
if (tempWordWidth + charWidth >= maxLineWidth) {
// this is to manage the casse where there is a set of long string without any blank space. In that case the text is broken and moved to next line.
wordEndIndex = c
lineObj.charEndIndex = c
tempWordWidth = 0
}
lineObj.plainContent = dataBlock.plainContent.slice(lineObj.charStartIndex, lineObj.charEndIndex + 1)
lines.push(lineObj)
lineObj = new getLineObj()
lineObj.maxLineWidth = maxLineWidth
lineObj.maxFontSize = style.fontSize
if (dataBlock.type == 1){
lineObj.tabWidth = tabWidth
lineObj.tabCount = dataBlock.tabCount
}else{
maxLineWidth = config.pageSetup.pxWidth - (config.format.margin * pxMmRatio * 2)
lineObj.tabWidth = 0
lineObj.tabCount = 0
}
lineObj.charStartIndex = wordEndIndex + 1
lineObj.charEndIndex = wordEndIndex + 1
tempLineWidth = tempWordWidth
}
else {
// if the char can fit in the same line. then it is well and good
tempLineWidth += charWidth
tempWordWidth += charWidth
}
}
lineObj.plainContent = dataBlock.plainContent.slice(lineObj.charStartIndex, lineObj.charEndIndex + 1)
// there is chance that the last line is not at the width// so we need to handle the last line separately
if (lineObj.charEndIndex <= dataBlock.plainContent.length) {
lineObj.charEndIndex = dataBlock.plainContent.length - 1
}
lineObj.plainContent = dataBlock.plainContent.slice(lineObj.charStartIndex, lineObj.charEndIndex)
lines.push(lineObj)
ctx.restore()
}
return
}
function renderLines() {
let x = 0, y = config.format.margin * pxMmRatio;
for (let l = 0; l < lines.length; l++) {
let setData = dataList[lines[l].dataIndex]
let ctx = pageList[0].el.getContext('2d', { willReadFrequently: true })
x = config.format.margin * pxMmRatio
x += lines[l].tabWidth
y = y + (lines[l].maxFontSize * pxMmRatio)
lines[l].y = y
ctx.save()
// this is to render the numbering and bullets etc
if (lines[l].dataType == 1) {
ctx.save()
ctx.font = `${setData.style.fontSize * pxMmRatio}px ${setData.style.fontFamily}`
ctx.fillStyle = `${setData.style.fontColor}`
let label = getLabel(lines[l].dataIndex)
let labelWidth = ctx.measureText(label).width
labelWidth += pxMmRatio * 5
ctx.fillText(label, x - labelWidth, y)
ctx.restore()
}
for (let c = lines[l].charStartIndex; c <= lines[l].charEndIndex; c++) {
let char = setData?.plainContent[c]
if (char) {
let style = setData.formatedText[c]
ctx.save()
ctx.font = `${style?.bold ? 'bold ' : ''}${style?.italic ? 'italic ' : ''} ${style.fontSize * pxMmRatio}px ${style.fontFamily}`
ctx.fillStyle = `${style?.fontColor}`
ctx.fillText(char, x, y)
setData.formatedText[c].x = x
setData.formatedText[c].y = y
ctx.restore()
if (setData.formatedText[c]?.width) {
x += setData.formatedText[c]?.width
}
}
}
ctx.restore()
}
return
}
function getLabel(dataIndex) {
let label = "x."
if (dataList[dataIndex].listStyle == 0) { // 1. a. i.
let tabCount = dataList[dataIndex].tabCount
tabCount = tabCount ? tabCount : 0
if (tabCount % 3 == 0) label = `${dataList[dataIndex].listItemNumber}.`
else if (tabCount % 3 == 1) label = `${convertToLetter(dataList[dataIndex].listItemNumber)}.`
else label = `${convertToRoman(dataList[dataIndex].listItemNumber).toLowerCase()}.`
}
else if (dataList[dataIndex].listStyle == 1) { // >
label = `>`
}
function convertToLetter(num) {
num = (num-1).toString(26)
let label = ""
for(let i=0;i<num.length;i++) label+= letters[num[i]]
return label
}
function convertToRoman(num) {
let result = '';
let divisor = 1000;
for (let i = 3; i >= 0; i--) {
const digit = Math.floor(num / divisor);
num %= divisor;
divisor /= 10;
if (digit > 0) {
if (digit === 9) {
result += romanNumerals[i][3];
} else if (digit >= 5) {
result += romanNumerals[i][2];
result += romanNumerals[i][0].repeat(digit - 5);
} else if (digit === 4) {
result += romanNumerals[i][1];
} else {
result += romanNumerals[i][0].repeat(digit);
}
}
}
return result;
}
return label
}
function getCharacterWidth(char, style) {
let canvas = pageList[0].el
let ctx = canvas.getContext('2d', { willReadFrequently: true })
ctx.save()
ctx.font = `${style?.bold ? 'bold ' : ''}${style?.italic ? 'italic ' : ''} ${style.fontSize * pxMmRatio}px ${style.fontFamily}`
ctx.fillStyle = `${style?.fontColor}`
let width = ctx.measureText(char).width
ctx.restore()
return width
}
function createNewPage() {
let canvas;
if (isModule) {
const { createCanvas } = require('canvas')
canvas = createCanvas(config.pageSetup.canvasWidth, config.pageSetup.canvasHeight)
} else {
canvas = document.createElement('canvas')
canvas.setAttribute('class', 'page')
canvas.width = config.pageSetup.canvasWidth
canvas.height = config.pageSetup.canvasHeight
canvas.setAttribute("tabIndex", -1)
canvas.setAttribute('tabIndex', '-1')
canvas.width = config.pageSetup.pxWidth
canvas.height = config.pageSetup.pxHeight
canvas.style.width = `${config.pageSetup.uiWidth}mm`
let ctx = canvas.getContext('2d', { willReadFrequently: true })
ctx.fillStyle = config.format.background
ctx.fillRect(0, 0, canvas.width, canvas.height)
canvas.addEventListener('keydown', keydownHandler)
canvas.addEventListener('mousedown', mousedownHandler)
canvas.addEventListener("focus", onFocusHandler)
canvas.addEventListener("blur", onBlurHandler)
}
return canvas
}
function keydownHandler(e) {
if (e.altKey) return
else if (e.key == 'Tab') {
e.preventDefault()
manageIndentation(e.shiftKey ? -1 : 1)
} else if ([']', '['].includes(e.key) && e.ctrlKey) {
manageIndentation(e.key == '[' ? -1 : 1)
}
else if (e.key == 'Backspace') {
if (caretData.index == 0) {
let activeDataIndex = dataList.findIndex(item => item.id == caretData.activeData.id)
if (caretData.activeData.tabCount) {
if (caretData.activeData.type==1){
caretData.activeData.type = 0
}else{
caretData.activeData.tabCount--
}
}
else if (activeDataIndex > 0) {
if (caretData.activeData.type == 0) {
if (!caretData.activeData.plainContent.length) { dataList.splice(activeDataIndex, 1) }
caretData.activeData = dataList[activeDataIndex - 1]
caretData.index = caretData.activeData.plainContent.length
} else if (caretData.activeData.type == 1) {
caretData.activeData.type = 0
}
}
} else {
caretData.activeData.plainContent = caretData.activeData.plainContent.slice(0, caretData.index - 1) + caretData.activeData.plainContent.slice(caretData.index)
caretData.activeData.formatedText.splice(caretData.index - 1, 1)
--caretData.index;
}
}
else if (e.key == 'Enter') {
let dataObj = {
id: ++counter,
type: caretData.activeData.type,
formatedText: [],
plainContent: "",
listStyle: caretData.activeData.listStyle,
tabCount: caretData.activeData.tabCount,
style: { ...caretData.activeData.style }
}
if (e.ctrlKey || e.metaKey) dataObj.newPage = true
caretData.index = 0
caretData.activeData = dataObj
if (dataObj.type == 0) dataObj.tabCount = 0
dataList.push(dataObj)
}
else if (e.key.length == 1 && !e.ctrlKey && !e.metaKey) { // displayable text
// ***** DISPLAYABLE TEXT **** //
e.preventDefault()
caretData.activeData.plainContent = caretData.activeData.plainContent.slice(0, caretData.index) + e.key + caretData.activeData.plainContent.slice(caretData.index)
caretData.activeData.formatedText.splice(caretData.index, 0, caretData.style)
++caretData.index
}
else if (e.key == 'ArrowLeft') {
if (caretData.index) {
--caretData.index
} else {
let dataIndex = dataList.findIndex(item => item.id == caretData.activeData.id)
if (dataIndex > 0) {
caretData.activeData = dataList[dataIndex - 1]
caretData.index = caretData.activeData.plainContent.length
}
}
}
else if (e.key == 'ArrowRight') {
if (caretData.index < caretData.activeData.plainContent.length) {
++caretData.index
} else {
let dataIndex = dataList.findIndex(item => item.id == caretData.activeData.id)
if (dataList[dataIndex + 1]) {
caretData.activeData = dataList[dataIndex + 1]
caretData.index = 0
}
}
}
else if (e.key == 'ArrowUp') {
console.log('Up')
}
else if (e.key == 'ArrowDown') {
console.log('Down')
}
function manageIndentation(value) {
caretData.activeData.tabCount += value
if (caretData.activeData.tabCount < 0) caretData.activeData.tabCount = 0
else if (caretData.activeData.tabCount > 5) caretData.activeData.tabCount = 5
return true
}
caretData.blink = false
caretData.previousCaret = null
reRenderCanvas()
}
function placeCaret(cursor = { x: 0, y: 0 }) {
let found = false
for (let l = 0; l < lines.length; l++) {
if ((cursor.y <= lines[l].y) && (cursor.y >= (lines[l].maxFontSize * pxMmRatio))) {
let dataSet = dataList[lines[l].dataIndex]
for (let c = lines[l].charStartIndex; c <= lines[l].charEndIndex; c++) {
if ((cursor.x >= dataSet.formatedText[c].x) && (cursor.x <= (dataSet.formatedText[c].width + dataSet.formatedText[c].x))) {
caretData.activeData = dataSet
if (cursor.x <= dataSet.formatedText[c].x + dataSet.formatedText[c].width / 2) {
caretData.index = c
} else {
caretData.index = c + 1
}
found = true
caretData.blink = false
renderCaret(true)
return
}
}
if (found) break; // break only when the caret index is determined
}
}
if (!found) {
console.log('no char found')
}
}
function mousedownHandler(e) {
if (focussedPage) {
const rect = e.target.getBoundingClientRect(); // Get the position of the canvas
const pxX = (e.clientX - rect.left) * config.pageSetup.pxWidth / rect.width;
const pxY = (e.clientY - rect.top) * config.pageSetup.pxHeight / rect.height;
placeCaret({ x: pxX, y: pxY })
let style = caretData.activeData.formatedText[caretData.index]
if (style) caretData.style = { ...caretData.style,
fontSize: style.fontSize,
}
shadow.querySelector('[adc="font-size-input"]').value = caretData.style.fontSize
}
}
function onFocusHandler(e) {
focussedPage = e.target
lastFocussedPage = e.target
reRenderCanvas()
}
function onBlurHandler(e) {
caretData.blink = true
focussedPage = null
}
}
function changeListStyle(){
let activeDataIndex = dataList.findIndex( item => item.id == caretData.activeData.id )
let d = activeDataIndex-1
while( dataList?.[d]?.type==1){
dataList[d].listStyle = caretData.activeData.listStyle
--d
}
d = activeDataIndex+1
while(dataList?.[d]?.type==1){
dataList[d].listStyle = caretData.activeData.listStyle
++d
}
}
function addFonts(paths, name) {
if (typeof paths == 'string') paths = [paths]
let fontObj = {
paths: [],
name: name,
}
if (!isModule) {
let linkString = ''
for (let i = 0; i < paths.length; i++) {
let format = paths[i].split('.');
format = format[format.length - 1]
fontObj.paths.push(paths[i])
linkString += `url(${paths[i]}) format("${format == 'ttf' ? 'truetype' : format}")${(i >= paths.length - 1) ? '' : ',\n'}`
}
const customFont = new FontFace(`${name}`, `${linkString}`);
customFont.load()
.then((loadedFont) => {
document.fonts.add(loadedFont);
loadedFont.loaded.then(() => {
fontList.push(fontObj)
reRenderFontDropdown()
})
})
}
function reRenderFontDropdown() {
let fontFamilyDropdown = headerToolbar.find(item => item.getAttribute('adc') == 'font-select')
if (fontFamilyDropdown) {
fontFamilyDropdown.innerHTML = ""
fontList.forEach((font, i) => {
let optionTag = document.createElement('option')
optionTag.setAttribute('value', font.name)
optionTag.style.fontFamily = font.name
optionTag.innerText = font.name
fontFamilyDropdown.append(optionTag)
})
}
changeFontFamily()
}
function changeFontFamily() {
let fontFamilyDropdown = headerToolbar.find(item => item.getAttribute('adc') == 'font-select')
if (fontFamilyDropdown) fontFamilyDropdown.value = caretData.style.fontFamily
}
}
function onMouseWheelHandler(e) {
if (e.ctrlKey || e.metaKey) e.preventDefault()
}
function addGlobalEvents(e) {
shadow.querySelector('.page-list').addEventListener('wheel', onMouseWheelHandler)
shadow.addEventListener('mousedown', globalMouseDownHandler)
}
function globalMouseDownHandler(e) {
var elem = e.target
var targetId = elem.getAttribute('adc-target')
if (elem.getAttribute('adc-type') == 'popover') { return }
while (!targetId && elem) {
elem = elem.parentNode
if (elem?.getAttribute?.('adc-type') == 'popover') return
targetId = elem?.getAttribute?.('adc-target')
}
let allPopovers = shadow.querySelectorAll('[adc-type="popover"]')
allPopovers.forEach(item => {
if (item.getAttribute('adc') == targetId) item.classList.toggle('show')
else item.classList.remove('show')
})
}
function renderCaret(toLoop) {
clearTimeout(caretData.timeout)
let ctx = pageList[0].el.getContext('2d', { willReadFrequently: true })
ctx.save()
if (caretData.previousCaret) {
ctx.putImageData(caretData.previousCaret.imageData, caretData.previousCaret.x, caretData.previousCaret.y);
caretData.previousCaret = null
}
if (!caretData.blink) {
let dataIndex = dataList.findIndex(item => item.id == caretData.activeData.id)
let lineObj = lines.find(item => item.dataIndex == dataIndex && caretData.index >= item.charStartIndex)
let x = (config.format.margin * pxMmRatio) + lineObj.tabWidth
let y = (config.format.margin) * pxMmRatio
let height = caretData.style.fontSize * pxMmRatio * 5 / 4
let width = height / 10
let charData = caretData.activeData.formatedText[caretData.index - 1]
if (lineObj) {
x = (charData?(charData.x+charData.width):x)
if(charData){
y = charData.y - (caretData.style.fontSize*pxMmRatio)
}else{
y = lineObj.y - (lineObj.maxFontSize*pxMmRatio)
}
}
const imageData = ctx.getImageData(x, y, width, height);
const data = imageData.data;
caretData.previousCaret = { imageData: structuredClone(imageData), x, y }
// Invert the color of the rectangular region
for (let i = 0; i < data.length; i += 4) {
data[i] = 255 - data[i]; // Red
data[i + 1] = 255 - data[i + 1]; // Green
data[i + 2] = 255 - data[i + 2]; // Blue
// Alpha channel remains unchanged (data[i + 3])
}
ctx.putImageData(imageData, x, y);
caretData.blink = true
} else {
caretData.blink = false
}
ctx.restore()
if (toLoop) {
caretData.timeout = setTimeout(() => {
renderCaret(true)
}, caretData.timeoutDuration)
}
return
}
function focusOnPage() {
caretData.blink = false
if (!lastFocussedPage) lastFocussedPage = pageList[0].el
if (lastFocussedPage) {
const scrollTop = pageScrollingDiv.scrollTop
lastFocussedPage.focus()
pageScrollingDiv.scrollTop = scrollTop
}
}
var returnObj = {
addFonts,
getConfiguration() { return JSON.parse(JSON.stringify(config)) },
getPages() {
let pagesToReturn = JSON.parse(JSON.stringify(pageList))
for (let i = 0; i < pagesToReturn.length; i++) pagesToReturn[i].canvas = pageList[i].canvas
return pagesToReturn
},
log() {
console.log('dataList', dataList)
console.log('lines', lines)
console.log('caretData', caretData)
}
}
initialize()
return returnObj
}
if (isModule) module.exports = ADocEditor
\ No newline at end of file
...@@ -4,10 +4,9 @@ ...@@ -4,10 +4,9 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Custom Document Editor</title> <title>Custom Document Editor</title>
<link rel="stylesheet" href="./assets/a-doc-editor2.css">
<script src="./assets/fontkit.umd.min.js"></script> <script src="./assets/fontkit.umd.min.js"></script>
<script src="./assets/pdf-lib.min.js" ></script> <script src="./assets/pdf-lib.min.js" ></script>
<script src="./assets/a-doc-editor2.js"></script> <script src="./assets/a-doc-editor.js"></script>
<script src="./assets/html-docx.min.js"></script> <script src="./assets/html-docx.min.js"></script>
<link rel="icon" id="favicon" href="favicon.svg" type="image/png"> <link rel="icon" id="favicon" href="favicon.svg" type="image/png">
......
...@@ -14,6 +14,7 @@ var editor = new ADocEditor({ ...@@ -14,6 +14,7 @@ var editor = new ADocEditor({
container: document.getElementById("user-container-for-editor") container: document.getElementById("user-container-for-editor")
}) })
var extractedData = null var extractedData = null
editor.focusOnPage()
function log() { function log() {
editor.log() editor.log()
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment