diff --git a/front/css/ttb.css b/front/css/ttb.css new file mode 100644 index 0000000000000000000000000000000000000000..1dd3d09815820f0aa4aaf64bd201959ed95e4a74 --- /dev/null +++ b/front/css/ttb.css @@ -0,0 +1,57 @@ +/* vertical styles: */ + +#vertical_text_input{ + writing-mode: vertical-lr; + text-orientation: upright; + width:auto!important; + height: auto; + white-space: nowrap!important; + margin: .375rem 0; +} +#vertical_text_input:focus{ + outline: none; +} + +#textInputBorderWrapper{ + box-sizing: content-box; + padding: 0 .75rem; +} + +#diplomatic-lines.ttb{ + writing-mode: vertical-lr; + text-orientation: upright; +} + +#diplomatic-lines.ttb div{ + text-align: left; + min-width:25px; + padding: 0; + margin: 0; + padding-top: 30px; + + position: relative; + white-space: nowrap; +} + +#diplomatic-lines.ttb div::before{ + writing-mode: horizontal-tb !important; + position: absolute; + display: block; /*to set size properties*/ + white-space: nowrap; + text-align: left; + + width: 100%; /*adapt to #diplomatic-lines.ttb div size - set to position:relative*/ + height: auto; + + margin-left: 0; + margin-right: .5rem; + margin-top: -30px; + margin-bottom: 20px; + + padding-left: .5rem; + padding-right: 0; + + border-bottom: 1px solid #ddd; + border-right: none!important; + border-right-width:0!important; +} diff --git a/front/src/main.js b/front/src/main.js index 70a201056ccc4f1ddadd03a3b1fccaf56e94d086..6701affcac361dec89604a6ac195371a52bab37e 100644 --- a/front/src/main.js +++ b/front/src/main.js @@ -1,5 +1,6 @@ import '../css/escriptorium.css'; import '../css/rtl.css'; +import '../css/ttb.css'; import './ajax.js'; import { Alert, bootWebsocket, joinDocumentRoom } from './messages.js'; window.Alert = Alert; @@ -29,4 +30,4 @@ import { bootImageCards } from './image_cards.js'; window.bootImageCards = bootImageCards; import { bootModels } from './models.js'; -window.bootModels = bootModels; \ No newline at end of file +window.bootModels = bootModels; diff --git a/front/vue/components/DiploPanel.vue b/front/vue/components/DiploPanel.vue index 4da1265f94f744da00cf08334cadcac945091fd8..82409240c3d97e54fa341ce7953f0d8dbfc8d93d 100644 --- a/front/vue/components/DiploPanel.vue +++ b/front/vue/components/DiploPanel.vue @@ -18,7 +18,9 @@ v-bind:key="'DL' + line.pk"> </diploline> - <div id="diplomatic-lines" + <!--adding a class to get styles for ttb direction:--> + <div :class="$store.state.document.mainTextDirection" + id="diplomatic-lines" ref="diplomaticLines" contenteditable="true" autocomplete="off" @@ -65,6 +67,9 @@ export default Vue.extend({ }.bind(this), 10000); }, mounted() { + // fix the original width so that when content texts are loaded/page refreshed with diplo panel, the panel width wont be bigger than other, especially for ttb text: + document.querySelector('#diplo-panel').style.width = document.querySelector('#diplo-panel').clientWidth + 'px'; + Vue.nextTick(function() { var vm = this ; vm.sortable = Sortable.create(this.$refs.diplomaticLines, { diff --git a/front/vue/components/TranscriptionModal.vue b/front/vue/components/TranscriptionModal.vue index 640ce673e574cbc89aa637747007f41d7c46f5bd..5cb968f7afa9f6580d7c78900bb27bcfc6a32970 100644 --- a/front/vue/components/TranscriptionModal.vue +++ b/front/vue/components/TranscriptionModal.vue @@ -70,7 +70,8 @@ </div> <div id="trans-input-container" ref="transInputContainer"> - <input v-on:keyup.down="$store.dispatch('lines/editLine', 'next')" + <input v-if="$store.state.document.mainTextDirection != 'ttb'" + v-on:keyup.down="$store.dispatch('lines/editLine', 'next')" v-on:keyup.up="$store.dispatch('lines/editLine', 'previous')" v-on:keyup.enter="$store.dispatch('lines/editLine', 'next')" id="trans-input" @@ -80,6 +81,30 @@ v-model.lazy="localTranscription" autocomplete="off" autofocus/> + <!--Hidden input for ttb text: --> + <input v-else + id="trans-input" + ref="transInput" + name="content" + type="hidden" + v-model.lazy="localTranscription" + autocomplete="off" /> + <!-- in this case, input field is replaced by: --> + <div v-if="$store.state.document.mainTextDirection == 'ttb'" + id="textInputWrapper"> + <div id="textInputBorderWrapper" class="form-control mb-2"> + <div v-on:blur="localTranscription = $event.target.textContent" + v-on:keyup="recomputeInputCharsScaleY()" + v-on:keyup.right="$store.dispatch('lines/editLine', 'next')" + v-on:keyup.left="$store.dispatch('lines/editLine', 'previous')" + v-on:keyup.enter="cleanHTMLTags();recomputeInputCharsScaleY();$store.dispatch('lines/editLine', 'next')" + v-html="localTranscription" + id="vertical_text_input" + contenteditable="true"> + </div> + </div> + </div> + <small v-if="line.currentTrans && line.currentTrans.version_updated_at" class="form-text text-muted"> <span>by {{line.currentTrans.version_author}} ({{line.currentTrans.version_source}})</span> <span>on {{momentDate}}</span> @@ -185,9 +210,27 @@ export default Vue.extend({ $(this.$refs.transModal).draggable({handle: '.modal-header'}); $(this.$refs.transModal).resizable(); this.computeStyles(); + let modele = this; let input = this.$refs.transInput; - input.focus(); + + // no need to make focus on hiden input with a ttb text + if(this.$store.state.document.mainTextDirection != 'ttb'){ + input.focus(); + }else{ // avoid some br or other html tag for a copied text on an editable input div (vertical_text_input): + // + document.getElementById("vertical_text_input").addEventListener("paste", function(e) { + + // cancel paste to treat its content before inserting it + e.preventDefault(); + + // get text representation of clipboard + var text = (e.originalEvent || e).clipboardData.getData('text/plain'); + this.innerHTML = text; + modele.recomputeInputCharsScaleY(); + + }, false); + } }, watch: { line(new_, old_) { @@ -240,7 +283,19 @@ export default Vue.extend({ close() { $(this.$refs.transModal).modal('hide'); }, - + cleanHTMLTags(){ + document.getElementById("vertical_text_input").innerHTML = document.getElementById("vertical_text_input").textContent; + }, + recomputeInputCharsScaleY(){ + + let inputHeight = document.getElementById("vertical_text_input").clientHeight; + let wrapperHeight = document.getElementById("textInputBorderWrapper").clientHeight; + let textScaleY = wrapperHeight / (inputHeight + 10); + + // to avoid input text outside the border box: + if(inputHeight > wrapperHeight) + document.getElementById("vertical_text_input").style.transform = "scaleY("+textScaleY+")"; + }, comparedContent(content) { if (!this.line.currentTrans) return; let diff = Diff.diffChars(this.line.currentTrans.content, content); @@ -286,6 +341,8 @@ export default Vue.extend({ // calculate rotation needed to get the line horizontal let target_angle = 0; // all lines should be topologically ltr + if(this.$store.state.document.mainTextDirection == 'ttb') // add a 90 angle for vertical texts + target_angle = 90; let angle = target_angle - this.getLineAngle(); // apply it to the polygon and get the resulting bbox @@ -309,8 +366,14 @@ export default Vue.extend({ let context = hContext*lineHeight; let visuHeight = lineHeight + 2*context; - modalImgContainer.style.height = visuHeight+'px'; + if(this.$store.state.document.mainTextDirection != 'ttb'){ + modalImgContainer.style.height = visuHeight+'px'; + }else{ + modalImgContainer.style.width = visuHeight+'px'; + modalImgContainer.style.display = 'inline-block'; + modalImgContainer.style.verticalAlign = 'top'; + } let top = -(bbox.top*ratio - context); let left = -(bbox.left*ratio - context); @@ -353,6 +416,8 @@ export default Vue.extend({ // Content input let container = this.$refs.transInputContainer; let input = container.querySelector('#trans-input'); + let verticalTextInput; + // note: input is not up to date yet let content = this.line.currentTrans && this.line.currentTrans.content || ''; let ruler = document.createElement('span'); @@ -360,13 +425,29 @@ export default Vue.extend({ ruler.style.visibility = 'hidden'; ruler.textContent = content; ruler.style.whiteSpace="nowrap" + + if(this.$store.state.document.mainTextDirection == 'ttb'){ + // put the container inline for vertical transcription: + container.style.display = 'inline-block'; + verticalTextInput = container.querySelector('#vertical_text_input'); + // apply vertical writing style to the ruler: + ruler.style.writingMode = 'vertical-lr'; + ruler.style.textOrientation = 'upright'; + } + container.appendChild(ruler); let context = hContext*lineHeight; let fontSize = Math.max(15, Math.round(lineHeight*0.7)); // Note could depend on the script ruler.style.fontSize = fontSize+'px'; - input.style.fontSize = fontSize+'px'; - input.style.height = Math.round(fontSize*1.1)+'px'; + + if(this.$store.state.document.mainTextDirection != 'ttb'){ + input.style.fontSize = fontSize+'px'; + input.style.height = Math.round(fontSize*1.1)+'px'; + }else{ + verticalTextInput.style.fontSize = fontSize+'px'; + verticalTextInput.style.width = Math.round(fontSize*1.1)+'px'; + } if (this.$store.state.document.readDirection == 'rtl') { container.style.marginRight = context+'px'; @@ -375,15 +456,40 @@ export default Vue.extend({ // TODO: deal with other directions container.style.marginLeft = context+'px'; } - if (content) { - let lineWidth = bbox.width*ratio; - var scaleX = Math.min(5, lineWidth / ruler.clientWidth); - scaleX = Math.max(0.2, scaleX); - input.style.transform = 'scaleX('+ scaleX +')'; - input.style.width = 100/scaleX + '%'; - } else { - input.style.transform = 'none'; - input.style.width = '100%'; //'calc(100% - '+context+'px)'; + if(this.$store.state.document.mainTextDirection != 'ttb'){ + if (content) { + let lineWidth = bbox.width*ratio; + var scaleX = Math.min(5, lineWidth / ruler.clientWidth); + scaleX = Math.max(0.2, scaleX); + input.style.transform = 'scaleX('+ scaleX +')'; + input.style.width = 100/scaleX + '%'; + } else { + input.style.transform = 'none'; + input.style.width = '100%'; //'calc(100% - '+context+'px)'; + } + }else{ + let modalImgContainer = this.$refs.modalImgContainer; + let textInputWrapper = container.querySelector('#textInputWrapper'); + let textInputBorderWrapper = container.querySelector('#textInputBorderWrapper'); + if (content) { + let lineWidth = bbox.height*ratio; + var scaleY = Math.min(5, lineWidth / ruler.clientHeight); + //var scaleY = Math.min(5, lineWidth / modalImgContainer.clientHeight); + //var scaleY = Math.min(5, modalImgContainer.clientHeight / ruler.clientHeight); + //var scaleY = Math.min(5, modalImgContainer.clientHeight / textInputWrapper.clientHeight) * 0.7; + scaleY = Math.max(0.2, scaleY); + verticalTextInput.style.transformOrigin = 'top'; + verticalTextInput.style.transform = 'scaleY('+ scaleY +')'; + //document.getElementById('vertical_text_input').style.height = 100/scaleY + '%'; // not needed here + } else { + verticalTextInput.style.transform = 'none'; + verticalTextInput.style.height = modalImgContainer.clientHeight + 'px'; + } + textInputWrapper.style.height = modalImgContainer.clientHeight + 'px'; + // simulate an input field border to fix it to the actual size of the image + textInputBorderWrapper.style.width = verticalTextInput.clientWidth+'px'; + //textInputBorderWrapper.style.width = verticalTextInput.offsetWidth+'px'; + textInputBorderWrapper.style.height = modalImgContainer.clientHeight+'px'; } container.removeChild(ruler); // done its job }, @@ -397,13 +503,33 @@ export default Vue.extend({ let bbox = this.getRotatedLineBBox(); let hContext = 0.3; // vertical context added around the line, in percentage - let ratio = modalImgContainer.clientWidth / (bbox.width + (2*bbox.height*hContext)); - let MAX_HEIGHT = Math.round(Math.max(25, (window.innerHeight-230) / 3)); - let lineHeight = Math.max(30, Math.round(bbox.height*ratio)); - if (lineHeight > MAX_HEIGHT) { - // change the ratio so that the image can not get too big - ratio = (MAX_HEIGHT/lineHeight)*ratio; - lineHeight = MAX_HEIGHT; + + // + let ratio = 1; + let lineHeight = 150; + + if(this.$store.state.document.mainTextDirection != 'ttb') + { + ratio = modalImgContainer.clientWidth / (bbox.width + (2*bbox.height*hContext)); + let MAX_HEIGHT = Math.round(Math.max(25, (window.innerHeight-230) / 3)); + lineHeight = Math.max(30, Math.round(bbox.height*ratio)); + if (lineHeight > MAX_HEIGHT) { + // change the ratio so that the image can not get too big + ratio = (MAX_HEIGHT/lineHeight)*ratio; + lineHeight = MAX_HEIGHT; + } + }else{ // permutation of sizes for ttb text + + modalImgContainer.style.height=String(window.innerHeight-230) + "px"; // needed to fix height or ratio is nulled + ratio = modalImgContainer.clientHeight / (bbox.height + (2*bbox.width*hContext)); + let MAX_WIDTH = 30; + lineHeight = Math.max(30, Math.round(bbox.width*ratio)); + + if (lineHeight > MAX_WIDTH) { + // change the ratio so that the image can not get too big + ratio = (MAX_WIDTH/lineHeight)*ratio; + lineHeight = MAX_WIDTH; + } } this.computeImgStyles(bbox, ratio, lineHeight, hContext); diff --git a/front/vue/components/VisuLine.vue b/front/vue/components/VisuLine.vue index 4e1182534f7fe52303056cda7cbcdb1d03caf8ce..8c98b1f0db3122337d0eb580bf75a6ce665aab55 100644 --- a/front/vue/components/VisuLine.vue +++ b/front/vue/components/VisuLine.vue @@ -10,14 +10,28 @@ fill="none" v-bind:stroke="pathStrokeColor" v-bind:d="baselinePoints"></path> + + <text :text-anchor="$store.state.document.defaultTextDirection == 'rtl' ? 'end' : ''" + ref="textElement" + lengthAdjust="spacingAndGlyphs" + v-if="$store.state.document.mainTextDirection != 'ttb'"> + <textPath v-bind:href="'#' + textPathId" + v-if="line.currentTrans"> + {{ line.currentTrans.content }} + </textPath> + </text> + <text :text-anchor="$store.state.document.defaultTextDirection == 'rtl' ? 'end' : ''" ref="textElement" - lengthAdjust="spacingAndGlyphs"> + rotate="-90" + font-size="1em" + v-else> <textPath v-bind:href="'#' + textPathId" v-if="line.currentTrans"> {{ line.currentTrans.content }} </textPath> </text> + </g> </template> @@ -26,6 +40,8 @@ import { LineBase } from '../../src/editor/mixins.js'; export default Vue.extend({ mixins: [LineBase], + mounted() { + }, watch: { 'line.currentTrans.content': function(n, o) { this.$nextTick(this.reset); @@ -41,18 +57,43 @@ export default Vue.extend({ let poly = this.line.mask.flat(1).map(pt => Math.round(pt)); var area = 0; // A = 1/2(x_1y_2-x_2y_1+x_2y_3-x_3y_2+...+x_(n-1)y_n-x_ny_(n-1)+x_ny_1-x_1y_n), - for (let i=0; i<poly.length; i++) { - let j = (i+1) % poly.length; // loop back to 1 - area += poly[i][0]*poly[j][1] - poly[j][0]*poly[i][1]; + + var liste = String(poly).split(","); + var indexCoordonnee = 0; + var arrayCoordonnees = new Array(); + var paire = []; + for(var i = 0; i < liste.length; i++){ + paire.push(liste[i]); + if(indexCoordonnee==0){ + indexCoordonnee = 1; + }else{ + indexCoordonnee = 0; + arrayCoordonnees.push(paire); + paire = new Array(); + } + } + + for (let i=0; i<arrayCoordonnees.length; i++) { + let j = (i+1) % arrayCoordonnees.length; // loop back to 1 + area += arrayCoordonnees[i][0]*arrayCoordonnees[j][1] - arrayCoordonnees[j][0]*arrayCoordonnees[i][1]; } + area = Math.abs(area*this.ratio); lineHeight = area / this.$refs.pathElement.getTotalLength(); + } else { lineHeight = 30; } + + lineHeight = Math.max(Math.round(lineHeight), 5) * 0.3; + + let ratio = 1/4; // more well suited for horizontal latin writings + if(this.$store.state.document.mainTextDirection == 'ttb') + ratio = 1/2; + + this.$refs.textElement.setAttribute("font-size", String(lineHeight * (ratio)) + 'px'); - lineHeight = Math.max(Math.min(Math.round(lineHeight), 100), 5); - this.$refs.textElement.style.fontSize = lineHeight * (1/2) + 'px'; + //return lineHeight+'px'; return 10+'px'; }, computeTextLength() {