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 { .toolbar .item {
left: -16%; position: relative;
transition: 300ms; background: #408640;
} border: 1px solid gray;
border-radius: 3px;
.a-doc-editor .body .toggle-sidebar { 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;
}
.a-doc-editor .body .right-sidebar.hide {
right: -16%;
transition: 300ms;
}
.a-doc-editor .body .sidebar-body{
flex: 1;
padding: 8px;
text-align: center;
overflow: auto;
display: flex;
flex-direction: column;
} }
.a-doc-editor .body .hide .sidebar-body{ .popover.show{
overflow: hidden; opacity: 1;
pointer-events: auto;
z-index: 10;
} }
.toolbar .item:hover {
.a-doc-editor .body .scrolling-area { background: #5fad5f;
position: absolute;
padding: 8px;
left: 5%;
right: 5%;
top: 0;
bottom: 0;
text-align: center;
overflow: auto;
} }
[adc-type="popover"] {
.a-doc-editor .body .scrolling-area canvas.page { pointer-events: none;
background: #f3f3f3;
margin-bottom: 10px;
width: 100%;
box-shadow: 4px 0px 10px 4px rgba(0, 0, 0, 0.4);
} }
.small-input {
.a-doc-editor .body .scrolling-area canvas.page:first-child { width: 50px;
margin-top: 10px;
} }
.small-btn {
.a-doc-editor ::-webkit-scrollbar { width: 25px;
width: 10px;
/* Set the width of the scrollbar */
} }
.option {
/* Thumb */ border: 1px solid black;
.a-doc-editor ::-webkit-scrollbar-thumb { padding: 5px;
background-color: #888; margin: 5px;
/* Set the color of the scrollbar thumb */ border-radius: 4px;
border-radius: 5px;
/* Set border radius */
} }
.option:hover {
/* Track */ background-color: #f5c468;
.a-doc-editor ::-webkit-scrollbar-track {
background-color: #f0f0f0;
/* Set the color of the scrollbar track */
border-radius: 5px;
/* Set border radius */
} }
.page-list {
.full-screen-overlay { position: relative;
position: fixed; display: block;
top: 0; overflow-y: auto;
left: 0; overflow-x: auto;
height: 100vh; background: #858585;
width: 100vw; gap: 20px;
background-color: rgba(0, 0, 0, 0.7); /* Optional background color */
display: flex;
justify-content: center;
align-items: center; align-items: center;
color: white;
font-size: xx-large;
} }
.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 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")
} else {
var { PDFDocument, rgb } = PDFLib
} }
var styleTag; 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" }
var screenWidth = (typeof screen != 'undefined') ? screen.width : 1280 const romanNumerals = [ ["I", "IV", "V", "IX"], ["X", "XL", "L", "XC"], ["C", "CD", "D", "CM"], ["M"] ];
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 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 class="option">
<button ade-action="font-size-minus">-</button>
<input ade-action="font-size-set"/>
<button ade-action="font-size-plus">+</button>
</div>
<div class="option" ade-action="font-bold">
<div class="option-button"><strong>B</strong></div>
</div>
<div class="option" ade-action="font-italic">
<div class="option-button"><em>I</em></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">
<div class="list-label">
<span>1&ndash;&ndash;&ndash;&ndash;</span>
<span>2&ndash;&ndash;&ndash;&ndash;</span>
<span>3&ndash;&ndash;&ndash;&ndash;</span>
</div>
<div class="thumbnail-options">
<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> </div>
</div> </div>
<div class="body"> <div class="item">
<div class="scrolling-area" id="${scrollingAreaId}"> <button adc="font-size-change" class="small-btn" value="-1">-</button>
</div> <input type="number" adc="font-size-input" class="small-input">
<div class="left-sidebar" id="${leftSidebarId}"> <button adc="font-size-change" class="small-btn" value="+1">+</button>
<div class="toggle-sidebar">
<span></span>
<span>Summary</span>
<span>&lrarr;</span>
</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>
</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-editor.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/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"]')
fontBold = container.querySelector('[ade-action="font-bold"]')
fontBold.addEventListener('click', toggleBold)
fontItalic = container.querySelector('[ade-action="font-italic"]')
fontItalic.addEventListener('click', toggleItalic)
fullScreenLoadingOverlay = document.createElement('div')
fullScreenLoadingOverlay.innerText = 'Loading...'
fullScreenLoadingOverlay.setAttribute('class', 'full-screen-overlay')
})()
!(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'] } }
bindGlobalEvents() 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`
}
reRenderPages(dataSet) pxMmRatio = config.pageSetup.pxWidth / config.pageSetup.mmWidth;
return config
} }
function reRenderPages(dataList, option) { function reRenderCanvas() {
if (isRendering) return
if (renderInProgress) return let pageIndex = 0
renderInProgress = true if (!pageList.length) {
pageList[pageIndex] = { id: ++counter, el: createNewPage(), dataIndex: 0 }
let canvasIndex = 0 shadow.querySelector('.page-list').append(pageList[pageIndex].el)
if (!canvasList.length) canvasList[0] = { el: createNewCanvas(), dataIndex: 0 } }
// 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) // to calculate the lines
if (dataBlock.tabCount || dataBlock.type == 1) { let tempLineWidth = 0;
let finalTabCount = dataBlock.tabCount 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) {
finalTabCount++ let previousBlock = dataList[d - 1]
} if (!previousBlock || previousBlock?.type != 1) dataBlock.listItemNumber = 1
let tabDistance = finalTabCount * config.format.tabWidth * config.pageSetup.canvasMultiplier else if (previousBlock.tabCount == dataBlock.tabCount) {
if (tabDistance > maxLineWidth * 5 / 6) { tabDistance = 0 } dataBlock.listItemNumber = previousBlock.listItemNumber + 1
maxLineWidth = maxLineWidth - tabDistance } else {
} dataBlock.listItemNumber = 1
lineObj.maxLineWidth = maxLineWidth let olderBlockIndex = d - 1
lineObj.tabCount = dataBlock.tabCount while (dataList?.[olderBlockIndex]?.type == 1 && dataList?.[olderBlockIndex]?.tabCount >= dataBlock.tabCount) {
if (dataList?.[olderBlockIndex]?.tabCount == dataBlock.tabCount) {
// // for checking the listIndex dataBlock.listItemNumber = dataList?.[olderBlockIndex].listItemNumber + 1
if (dataBlock.type == 1) { break;
for (let i = dataSetIndex - 1; i >= 0; i--) { }
if (dataList[i].type != 1) { --olderBlockIndex;
dataBlock.listIndex = 0 }
break;
} else if (dataList[i].tabCount == dataBlock.tabCount) {
dataBlock.listIndex = dataList[i].listIndex + 1
break
} else if (dataList[i].tabCount < dataBlock.tabCount) {
dataBlock.listIndex = 0
break;
} }
} }
} for (c = 0; c < dataBlock.plainContent.length; c++) {
lineObj.listIndex = dataBlock.listIndex let style = dataBlock?.formatedText?.[c]
lineObj.maxFontSize = (lineObj.maxFontSize<dataBlock?.formatedText?.[c].fontSize)?dataBlock?.formatedText?.[c].fontSize:lineObj.maxFontSize
let i = 0
for (i = 0; i < dataBlock.plainContent.length; i++) { if (/\s/.test(dataBlock.plainContent[c])) {
let style = dataBlock?.formatedText?.[i] // condition to check if a blank character is found.
if (!style) style = JSON.parse(JSON.stringify(config.style)) wordEndIndex = c
if (/\s/.test(dataBlock.plainContent[i])) { lineObj.charEndIndex = c
wordEndIndex = i tempWordWidth = 0
lineObj.charEndIndex = wordEndIndex; }
} let charWidth = getCharacterWidth(dataBlock.plainContent[c], style)
let charWidth = getCharacterWidth(canvasIndex, dataBlock.plainContent[i], style)
dataBlock.formatedText[i] = {
...config.style,
...dataBlock.formatedText[i],
width: charWidth
}
lineObj.maxFontSize = (!lineObj.maxFontSize)?style.fontSize:( style.fontSize>lineObj.maxFontSize?style.fontSize:lineObj.maxFontSize ) dataBlock.formatedText[c] = {
// lineObj.fontSize = style.fontSize ...config.style,
tempLineWidth += charWidth ...style,
if (tempLineWidth <= maxLineWidth) { width: charWidth
// can be added to the line // }
} else {
// cannot add this// new line should be added//
i = wordEndIndex;
lineObj.plainContent = dataBlock.plainContent.slice(lineObj.charStartIndex, lineObj.charEndIndex + 1)
lineObj = new getLineObj()
lineObj.listIndex = dataBlock.listIndex
lineObj.maxLineWidth = maxLineWidth
lineObj.tabCount = dataBlock.tabCount
lineObj.charStartIndex = i
lineObj.charEndIndex = i
dataLineArr.push(lineObj)
tempLineWidth = 0
}
} // // if with the current char the string could not fit on one line
lineObj.plainContent = dataBlock.plainContent.slice(lineObj.charStartIndex, lineObj.charEndIndex + 1) if (tempLineWidth + charWidth > maxLineWidth) {
// there is chance that the last line is not at the width// so we need to handle the last line separately // cannot add this// new line should be added//
if (lineObj.charEndIndex <= dataBlock.plainContent.length) {
lineObj.charEndIndex = dataBlock.plainContent.length - 1
}
lineObj.plainContent = dataBlock.plainContent.slice(lineObj.charStartIndex, lineObj.charEndIndex)
lines.push(...dataLineArr) 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
}
ctx.restore() lineObj.plainContent = dataBlock.plainContent.slice(lineObj.charStartIndex, lineObj.charEndIndex + 1)
return true 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
}
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) lineObj.plainContent = dataBlock.plainContent.slice(lineObj.charStartIndex, lineObj.charEndIndex + 1)
if (lines[l].blockStart && l != 0) y += lines[l].maxFontSize // there is chance that the last line is not at the width// so we need to handle the last line separately
if ((maxVericalWidth + lines[l].maxFontSize) < (y - lines[l].maxFontSize)) { if (lineObj.charEndIndex <= dataBlock.plainContent.length) {
canvasIndex++ lineObj.charEndIndex = dataBlock.plainContent.length - 1
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)
} }
lineObj.plainContent = dataBlock.plainContent.slice(lineObj.charStartIndex, lineObj.charEndIndex)
lines.push(lineObj)
let setData = dataSet[lines[l].dataSetIndex] 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 lines[l].y = y
if (lines[l].listIndex >= 0) { ctx.save()
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.fillStyle = `${style.fontColor}`
// 10 * Math.PI/180 = 0.174
ctx.setTransform(1, 0.174, 0, 1, 0, 0)
ctx.font = `${style.bold ? 'bold ' : ''} ${style.fontSize * config.pageSetup.fontMultiplier}px ${style.fontFamily}`
ctx.fillText(`${lines[l].listIndex + 1}.`, numberX, y)
ctx.restore()
}
// 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()
} }
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,414 +424,275 @@ var ADocEditor = function (customConfig) { ...@@ -474,414 +424,275 @@ 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 (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); if (digit > 0) {
const data = imageData.data; 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);
}
}
}
// Invert the color of the rectangular region return result;
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 }
} 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 return canvas
} }
function onFocusHandler(e) {
focusedCanvas = e.target
caretData.blink = false
reRenderPages(dataSet)
}
function onBlurHandler(e) {
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)) {
return
}
else if (e.keyCode == 16) { // only shift key
return
}
// 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 (e.keyCode == 9) {// tab input // no need to add case for casesensitivity
e.preventDefault() e.preventDefault()
} manageIndentation(e.shiftKey ? -1 : 1)
else if (e.keyCode == 13) { // Enter Key } else if ([']', '['].includes(e.key) && e.ctrlKey) {
if (caretData) { manageIndentation(e.key == '[' ? -1 : 1)
if (caretData.activeData.type == 0 || caretData.activeData.type == 1) { // for plain text // just go to next line }
let style = caretData?.activeData?.formatedText?.[caretData.activeData.formatedText.length - 1] else if (e.key == 'Backspace') {
if (!style) style = caretData?.activeData?.style if (caretData.index == 0) {
if (!style) style = config.style let activeDataIndex = dataList.findIndex(item => item.id == caretData.activeData.id)
if (caretData.activeData.tabCount) {
let newLineData = { if (caretData.activeData.type==1){
id: ++counter, caretData.activeData.type = 0
type: caretData.activeData.type, }else{
plainContent: "", caretData.activeData.tabCount--
style: JSON.parse(JSON.stringify(style))
} }
if (typeof caretData.activeData.tabCount == 'number') newLineData.tabCount = caretData.activeData.tabCount
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
} }
} else if (activeDataIndex > 0) {
} if (caretData.activeData.type == 0) {
else if (e.key.length == 1 || e.keyCode == 32) { // these are printable characters key if (!caretData.activeData.plainContent.length) { dataList.splice(activeDataIndex, 1) }
e.preventDefault() caretData.activeData = dataList[activeDataIndex - 1]
caretData.style.fontFamily = fontFamily.innerText caretData.index = caretData.activeData.plainContent.length
caretData.style.fontSize = Number(fontSize.value) } else if (caretData.activeData.type == 1) {
caretData.style.fontColor = fontColor.value caretData.activeData.type = 0
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.index
}
else if (e.keyCode == 8) { // backspace
if (caretData.index <= 0) {
let currentIndex = dataSet.findIndex(item => item.id == caretData.activeData.id)
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) caretData.activeData.plainContent = caretData.activeData.plainContent.slice(0, caretData.index - 1) + caretData.activeData.plainContent.slice(caretData.index)
caretData.index = caretData.index - 1 caretData.activeData.formatedText.splice(caretData.index - 1, 1)
caretData.activeData.formatedText.splice( caretData.index, 1 ) --caretData.index;
} }
} }
else if (e.keyCode == 37) { // left key 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() e.preventDefault()
if (caretData.index <= 0) { caretData.activeData.plainContent = caretData.activeData.plainContent.slice(0, caretData.index) + e.key + caretData.activeData.plainContent.slice(caretData.index)
let currentIndex = dataSet.findIndex(item => item.id == caretData.activeData.id) caretData.activeData.formatedText.splice(caretData.index, 0, caretData.style)
let previousData = dataSet[currentIndex - 1] ++caretData.index
if (previousData) { }
caretData.activeData = previousData else if (e.key == 'ArrowLeft') {
caretData.index = previousData.plainContent.length if (caretData.index) {
} --caretData.index
caretData.canvasIndex = canvasList.findIndex(item => item.el == e.target)
} else { } else {
caretData.index = (caretData.index <= 0) ? 0 : caretData.index - 1 let dataIndex = dataList.findIndex(item => item.id == caretData.activeData.id)
if (dataIndex > 0) {
caretData.activeData = dataList[dataIndex - 1]
caretData.index = caretData.activeData.plainContent.length
}
} }
reRenderPages(dataSet, { onlyCursor: true })
return
}
else if (e.keyCode == 38) { // up key
e.preventDefault()
} }
else if (e.keyCode == 39) { // right key else if (e.key == 'ArrowRight') {
e.preventDefault() if (caretData.index < caretData.activeData.plainContent.length) {
if (caretData.index <= caretData.activeData.plainContent.length - 1) { ++caretData.index
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 { } // unhandled cases else if (e.key == 'ArrowDown') {
console.log('Down')
reRenderPages(dataSet)
clearInterval(caretData.interval)
}
function setCaretPosition(e) {
let canvasIndex = canvasList.findIndex(item => item.el == e.target)
let rect = e.target.getBoundingClientRect()
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 manageIndentation(value) {
let thisLine = (item.y + item.maxFontSize / 4) > position.y && canvasIndex == item.canvasIndex caretData.activeData.tabCount += value
if (thisLine) return true if (caretData.activeData.tabCount < 0) caretData.activeData.tabCount = 0
else if (!(lines[i + 1]) || (lines[i + 1]?.canvasIndex > canvasIndex)) return true else if (caretData.activeData.tabCount > 5) caretData.activeData.tabCount = 5
return false return true
})
if (closestLine) {
caretData.activeData = dataSet[closestLine.dataSetIndex]
let charIndex = closestLine.charStartIndex
let found = false
for (let i = charIndex; (i <= closestLine.charEndIndex && caretData.activeData.formatedText[i].y == closestLine.y); i++) {
let formatedText = caretData.activeData.formatedText[i]
if (position.x < (formatedText.x + formatedText.width)) {
charIndex = i
found = true
break;
}
charIndex = i + 1
}
if (/\s+/.test(caretData.activeData.plainContent[charIndex]) && caretData.activeData.plainContent[charIndex + 1] && charIndex == closestLine.charStartIndex) {
charIndex++
}
caretData.index = charIndex
} }
reRenderPages(dataSet)
}
caretData.blink = false
function mousedownHandler(e) { caretData.previousCaret = null
e.target.focus({ preventScroll: true }); reRenderCanvas()
setCaretPosition(e)
}
renderInProgress = false
}
function removeStyles(dataList) {
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)))
}
}
}
async function generatePDF(file) {
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 function placeCaret(cursor = { x: 0, y: 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) let found = false
if (lines[l].blockStart && l != 0) y += lines[l].maxFontSize for (let l = 0; l < lines.length; l++) {
if ((maxVericalWidth + lines[l].maxFontSize) < (y - lines[l].maxFontSize)) { if ((cursor.y <= lines[l].y) && (cursor.y >= (lines[l].maxFontSize * pxMmRatio))) {
canvasIndex++ let dataSet = dataList[lines[l].dataIndex]
page = pdfDoc.addPage([config.pageSetup.width, config.pageSetup.height]) for (let c = lines[l].charStartIndex; c <= lines[l].charEndIndex; c++) {
y = lines[l].maxFontSize + (config.format.margin * config.pageSetup.canvasMultiplier) 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
}
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) if (found) break; // break only when the caret index is determined
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 ) ),
})
} }
} }
lines[l].x = x if (!found) {
lines[l].canvasIndex = canvasIndex console.log('no char found')
for (let c = lines[l].charStartIndex; c <= lines[l].charEndIndex; c++) {
let char = setData?.plainContent[c]
if (char) {
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
}
}
} }
} }
const pdfBytes = await pdfDoc.save() function mousedownHandler(e) {
const blob = new Blob([pdfBytes], { type: "application/pdf" }); if (focussedPage) {
const downloadLink = document.createElement("a"); const rect = e.target.getBoundingClientRect(); // Get the position of the canvas
document.body.appendChild(downloadLink); const pxX = (e.clientX - rect.left) * config.pageSetup.pxWidth / rect.width;
downloadLink.href = URL.createObjectURL(blob); const pxY = (e.clientY - rect.top) * config.pageSetup.pxHeight / rect.height;
downloadLink.download = file; // Set desired filename placeCaret({ x: pxX, y: pxY })
downloadLink.style.display = "none"; // Keep it hidden let style = caretData.activeData.formatedText[caretData.index]
downloadLink.click() if (style) caretData.style = { ...caretData.style,
document.body.removeChild(downloadLink) fontSize: style.fontSize,
hideLoader() }
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 getRgbArrayFromHex(hexString){ function changeListStyle(){
let rgbArr= [ 0,0,0 ] let activeDataIndex = dataList.findIndex( item => item.id == caretData.activeData.id )
if (hexString.length==4 || hexString.length==5){ let d = activeDataIndex-1
rgbArr = [ parseInt(hexString[1], 16)/15, parseInt(hexString[2], 16)/15, parseInt(hexString[3], 16)/15 ]
}else if (hexString.length==7 || hexString.length==9){ while( dataList?.[d]?.type==1){
rgbArr = [ parseInt(hexString.slice(1,3), 16)/255, parseInt(hexString.slice(3,5), 16)/255, parseInt(hexString.slice(5,7), 16)/255 ] dataList[d].listStyle = caretData.activeData.listStyle
--d
}
d = activeDataIndex+1
while(dataList?.[d]?.type==1){
dataList[d].listStyle = caretData.activeData.listStyle
++d
} }
return rgbArr
} }
function addFonts(paths, name) { function addFonts(paths, name) {
if (typeof paths == 'string') paths = [paths] if (typeof paths == 'string') paths = [paths]
let fontObj = { let fontObj = {
...@@ -909,179 +720,132 @@ var ADocEditor = function (customConfig) { ...@@ -909,179 +720,132 @@ var ADocEditor = function (customConfig) {
} }
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')
// // these are methods related to the toolbar items optionTag.setAttribute('value', font.name)
function changeFontFamily(e) { optionTag.style.fontFamily = font.name
if (e) { optionTag.innerText = font.name
caretData.style.fontFamily = e.target.innerText fontFamilyDropdown.append(optionTag)
caretData.styleModified = false })
focusLastCanvas()
if (!caretData.activeData.plainContent.length) {
caretData.activeData.style.fontFamily = e.target.innerText
} }
} else { changeFontFamily()
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 changeFontFamily() {
} let fontFamilyDropdown = headerToolbar.find(item => item.getAttribute('adc') == 'font-select')
if (fontFamilyDropdown) fontFamilyDropdown.value = caretData.style.fontFamily
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 onMouseWheelHandler(e) {
if (e.ctrlKey || e.metaKey) e.preventDefault()
}
function reRenderFontDropdown() { function addGlobalEvents(e) {
if (fontFamilyDropdown) { shadow.querySelector('.page-list').addEventListener('wheel', onMouseWheelHandler)
fontFamilyDropdown.innerHTML = "" shadow.addEventListener('mousedown', globalMouseDownHandler)
fontList.forEach((font, i) => { }
let optionTag = document.createElement('div') function globalMouseDownHandler(e) {
optionTag.setAttribute('ade-type', 'option') var elem = e.target
optionTag.setAttribute('class', 'dropdown-option') var targetId = elem.getAttribute('adc-target')
optionTag.setAttribute('value', font.name) if (elem.getAttribute('adc-type') == 'popover') { return }
optionTag.style.fontFamily = font.name while (!targetId && elem) {
optionTag.value = font.name elem = elem.parentNode
optionTag.innerText = font.name if (elem?.getAttribute?.('adc-type') == 'popover') return
fontFamilyDropdown.append(optionTag) targetId = elem?.getAttribute?.('adc-target')
})
} }
changeFontFamily() 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 bindGlobalEvents() {
let options = container.querySelectorAll('[ade-target]') function renderCaret(toLoop) {
options.forEach(option => { clearTimeout(caretData.timeout)
let target = container.querySelector(option.getAttribute('ade-target')) let ctx = pageList[0].el.getContext('2d', { willReadFrequently: true })
option.innerText = target?.children?.[0]?.innerText ? target?.children?.[0]?.innerText : '' ctx.save()
}) if (caretData.previousCaret) {
fontSize.value = caretData.style.fontSize ctx.putImageData(caretData.previousCaret.imageData, caretData.previousCaret.x, caretData.previousCaret.y);
fontColor.value = caretData.style.fontColor caretData.previousCaret = null
fontColorLabel.style.backgroundColor = caretData.style.fontColor }
if (!caretData.blink) {
container.addEventListener('click', (e) => { let dataIndex = dataList.findIndex(item => item.id == caretData.activeData.id)
if (e.target.getAttribute('ade-type') == 'option') { let lineObj = lines.find(item => item.dataIndex == dataIndex && caretData.index >= item.charStartIndex)
let parent = e.target.parentElement let x = (config.format.margin * pxMmRatio) + lineObj.tabWidth
let labelList = container.querySelectorAll(`[ade-target="#${parent.id}"]`) let y = (config.format.margin) * pxMmRatio
labelList.forEach(item => { let height = caretData.style.fontSize * pxMmRatio * 5 / 4
item.innerText = e.target.innerText let width = height / 10
})
closeAllExcept() let charData = caretData.activeData.formatedText[caretData.index - 1]
} else { if (lineObj) {
let target = container.querySelector(e.target.getAttribute('ade-target')) x = (charData?(charData.x+charData.width):x)
closeAllExcept(target) if(charData){
y = charData.y - (caretData.style.fontSize*pxMmRatio)
}else{
y = lineObj.y - (lineObj.maxFontSize*pxMmRatio)
}
} }
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 closeAllExcept(except) {
let dropdownLists = container.querySelectorAll('[ade-type="dropdown"]') const imageData = ctx.getImageData(x, y, width, height);
dropdownLists.forEach(item => { const data = imageData.data;
if (item == except) { caretData.previousCaret = { imageData: structuredClone(imageData), x, y }
item.classList.toggle('show') // Invert the color of the rectangular region
} for (let i = 0; i < data.length; i += 4) {
else item.classList.remove('show') 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 {
function showLoader(){ caretData.blink = false
container.append(fullScreenLoadingOverlay) }
fullScreenLoadingOverlay.addEventListener('mousedown', preventEvents, true)
fullScreenLoadingOverlay.addEventListener('keydown', preventEvents, true)
}
function hideLoader(){
fullScreenLoadingOverlay.removeEventListener('mousedown', preventEvents, true)
fullScreenLoadingOverlay.removeEventListener('keydown', preventEvents, true)
fullScreenLoadingOverlay.remove()
}
function preventEvents(event) {
event.preventDefault();
event.stopPropagation();
}
function setupListItem(){
}
inititalize(customConfig) ctx.restore()
function destory() { if (toLoop) {
clearInterval(caretData.interval) caretData.timeout = setTimeout(() => {
renderCaret(true)
}, caretData.timeoutDuration)
}
return
} }
function focusOnPage() {
function getContent() { caretData.blink = false
return JSON.parse(JSON.stringify(dataSet)) 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