I want to build an InDesign script which when you import html tags such as bold and italics, those words are converted to bold or italics.
e.g.
I really like <strong>walking to the park</strong> and eating icecream would be:
I really like walking to the park and eating icecream.
However, In my textframe I can only get a whole paragraph to pick up a style and not individual words or phrases.
In the example below, I would want to apply a bold style to the secondPhrase variable (I have no problem stripping out the tags etc - just want to know how I can apply a style to JUST the secondPhrase
with (myElement) {
// Apply basic text style
applyParagraphStyle(myDoc.paragraphStyles.item("DefaultText"));
var firstPhrase = "I really like";
var secondPhrase = " walking to the park";
var thirdPhrase = " and eating icecream";
contents = firstLine + secondLine + thirdPhrase;
}
Something like the code below should do the trick. The insertionPoint acts just like the cursor in InDesign. If you keep grabbing the last insertionPoint you can change it's style while you are adding text.
var doc = app.documents.add();
var frame = doc.pages[0].textFrames.add({
geometricBounds: [6, 6, 40, 40]
});
var bold = doc.characterStyles.add({ name: "Bold", fontStyle: "Bold" });
var none = doc.characterStyles.itemByName("[None]");
frame.insertionPoints.lastItem().applyCharacterStyle(none);
frame.insertionPoints.lastItem().contents = "I really like";
frame.insertionPoints.lastItem().applyCharacterStyle(bold);
frame.insertionPoints.lastItem().contents = " walking to the park";
frame.insertionPoints.lastItem().applyCharacterStyle(none);
frame.insertionPoints.lastItem().contents = " and eating icecream";
Here's a DRY-er take on Josh's solution -- insertion points are the way to go. If you need to manipulate, mirror, or are working with dynamic JSON content / want to separate out your text, you'll need to run it through a loop, and simply assign your chunks of text as objects. This makes it much easier to swap out text and make use of some things like Array.prototype.reverse(), which I needed to use in my case.
Here's my take on it:
// Basic Setup
var doc = app.documents.add();
var myPage = doc.pages.item(0); // Arbitrary page
// Character-Styles
var bold = doc.characterStyles.add({ name: "Bold", fontStyle: "Bold" });
var none = doc.characterStyles.itemByName("[None]");
// My String as an Array of Objects (Basic JSON format)
var myStringsAndStyles = [
{
contents: "I really like",
characterStyle: none
},
{
contents: " walking to the park ",
characterStyle: bold
},
{
contents: "and eating ice cream.",
characterStyle: none
}
];
// Do stuff
with ( myPage) {
var myTextFrame = textFrames.add({
geometricBounds: [6, 6, 40, 40] // Arbitrary coords
});
with ( myTextFrame.insertionPoints ) {
for ( var i = 0, arrlength = myStringsAndStyles.length; i < arrlength; i++ ) {
lastItem().properties = {
contents: myStringsAndStyles[i].contents,
appliedCharacterStyle: myStringsAndStyles[i].characterStyle
}
}
}
}
Ta da!
Related
I'm doing something With an ESP8266, and I need some help with the HTML part of it.
I want to duplicate 1 slider:
The code is :
client.println("<!DOCTYPE html><html>");
client.println("<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">");
client.println("<link rel=\"icon\" href=\"data:,\">");
// CSS to style the on/off buttons
// Feel free to change the background-color and font-size attributes to fit your preferences
client.println("<style>body { text-align: center; font-family: \"Trebuchet MS\", Arial; margin-left:auto; margin-right:auto;}");
client.println(".slider { width: 300px; }</style>");
client.println("<script src=\"https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js\"></script>");
// Web Page
//client.println("</head><body><h1>ESP32 with Servo</h1>");
client.println("<p>Start Temperature: <span id=\"servoPos\"></span></p>");
client.println("<input type=\"range\" min=\"20\" max=\"30\" class=\"slider\" id=\"servoSlider\" onchange=\"servo(this.value)\" value=\""+valueString+"\"/>");
client.println("<script>var slider = document.getElementById(\"servoSlider\");");
client.println("var servoP = document.getElementById(\"servoPos\"); servoP.innerHTML = slider.value;");
client.println("slider.oninput = function() { slider.value = this.value; servoP.innerHTML = this.value; }");
client.println("$.ajaxSetup({timeout:1000}); function servo(pos) { ");
client.println("$.get(\"/?value=\" + pos + \"&\"); {Connection: close};}</script>");
client.println("</body></html>");
//GET /?value=180& HTTP/1.1
if(header.indexOf("GET /?value=")>=0) {
pos1 = header.indexOf('=');
pos2 = header.indexOf('&');
valueString = header.substring(pos1+1, pos2);
//Rotate the servo
//myservo.write(valueString.toInt());
// Serial.println(valueString);
}
}
}
// Clear the header variable
header = "";
This gives me a slider, but I want another one for controlling a different thing, under a different variable.
Since I don't really understand HTML, I have tried to change some variables and duplicate the code myself, but without success. The slider works, but I get a lot of errors.
Second (independent) slider
client.println("<p>Start Sensor2: <span id=\"servoPos2\"></span></p>");
client.println("<input type=\"range\" min=\"20\" max=\"30\" class=\"slider\" id=\"servoSlider2\" onchange=\"servo2(this.value)\" value=\""+valueString+"\"/>");
client.println("<script>var slider2 = document.getElementById(\"servoSlider2\");");
client.println("var servoP2 = document.getElementById(\"servoPos2\"); servoP2.innerHTML = slider2.value;");
client.println("slider2.oninput = function() { slider2.value = this.value; servoP2.innerHTML = this.value; }");
The function servo(pos) (my guess) in the rest code not shown has either to be
duplicated or ???
to stop guessing and helping you please add the following info:
Source code esp8266 or if it is a standard example the name of the ino file
The type of error- during compilation, on the browser console, on serial port?
I'd like users to enter a code and to assist them in transcribing it I'd hope to increase the spacing between every 3rd character they type. I've seen this nicely done for credit cards having 4 character spacing. This will be for an Ionic app so the simple input box coud be replaced with a customised Ionic control.
What methods have you used for this and what works best?
Open to Angular/Ionic code samples or a related web site tutorial.
Pure CSS would be nice.
Here is an other version, without jquery, works with alphanumerical and takes a configurable separator:
Typescript:
GROUP_SEPARATOR=" ";
......
format(valString) {
if (!valString) {
return '';
}
let val = valString.toString();
const parts = val.replace(/ /g, '');
return parts.replace(/\B(?=(?:\w{3})+(?!\w))/g, this.GROUP_SEPARATOR)
};
HTML
<input [(ngModel)]="input"
style="border:1px solid black" #myBudget="ngModel" (input)="input = format(input)">
DEMO
You can add space on keyup event.
Example
$('#input').on('keyup', function(e){
var val = $(this).val();
var newval = '';
val = val.replace(/\s/g, '');
for(var i=0; i < val.length; i++) {
if(i%3 == 0 && i > 0) newval = newval.concat(' ');
newval = newval.concat(val[i]);
}
$(this).val(newval);
})
I found a simpler method based on Vija's method ... Basically we match 3 non-space chars and we remove any previously added space chars. This is needed to allow the user to update or erase any chars in the text box.
A final solution may also need to adjust the position of the cursor based on where it was prior to performing the replace.
$('#input').on('keyup', function(e){
var val = $(this).val();
var newval = val.replace(/([^ ][^ ][^ ]) */g, "\$1 ").trim();
$(this).val(newval);
})
Let's assume that we have first paragraph in our google document:
Wo1rd word so2me word he3re last.
We need to search and replace some parts of text but it must be highlighted in editions history just like we changed only that parts and we must not loose our format (bold, italic, color etc).
What i have/understood for that moment: capturing groups didn't work in replaceText() as described in documentation. We can use pure js replace(), but it can be used only for strings. Our google document is array of objects, not strings. So i did a lot of tries and stopped at that code, attached in this message later.
Can't beat: how i can replace only part of what i've found. Capturing groups is very powerful and suitable instrument, but i can't use it for replacement. They didn't work or i can replace whole paragraph, that is unacceptable because of editions history will show full paragraph replace and paragraphs will lose formatting. What if what we searching will be in each and every paragraph, but only one letter must be changed? We will see full document replacement in history and it will be hard to find what really changed.
My first idea was to compare strings, that replace() gives to me with contents of paragraph then compare symbol after symbol and replace what is different, but i understand, that it will work only if we are sure that only one letter changed. But what if replace will delete/add some words, how it can be synced? It will be a lot bigger problem.
All topics that i've found and read triple times didn't helped and didn't moved me from the dead point.
So, is there any ideas how to beat that problem?
function RegExp_test() {
var docParagraphs = DocumentApp.getActiveDocument().getBody().getParagraphs();
var i = 0, text0, text1, test1, re, rt, count;
// equivalent of .asText() ???
text0 = docParagraphs[i].editAsText(); // obj
// equivalent of .editAsText().getText(), .asText().getText()
text1 = docParagraphs[i].getText(); // str
if (text1 !== '') {
re = new RegExp(/(?:([Ww]o)\d(rd))|(?:([Ss]o)\d(me))|(?:([Hh]e)\d(re))/g); // v1
// re = new RegExp(/(?:([Ww]o)\d(rd))/); // v2
count = (text1.match(re) || []).length; // re v1: 7, re v2: 3
if (count) {
test1 = text1.match(re); // v1: ["Wo1rd", "Wo", "rd", , , , , ]
// for (var j = 0; j < count; j++) {
// test1 = text1.match(re)[j];
// }
text0.replaceText("(?:([Ww]o)\\d(rd))", '\1-A-\2'); // GAS func
// #1: \1, \2 etc - didn't work: " -A- word so2me word he3re last."
test1 = text0.getText();
// js func, text2 OK: "Wo1rd word so-B-me word he3re last.", just in memory now
text1 = text1.replace(/(?:([Ss]o)\d(me))/, '$1-B-$2'); // working with str, not obj
// rt OK: "Wo1rd word so-B-me word he-C-re last."
rt = text1.replace(/(?:([Hh]e)\d(re))/, '$1-C-$2');
// #2: we used capturing groups ok, but replaced whole line and lost all formatting
text0.replaceText(".*", rt);
test1 = text0.getText();
}
}
Logger.log('Test finished')
}
Found a solution. It's a primitive enough but it can be a base for a more complex procedure that can fix all occurrences of capture groups, detect them, mix them etc. If someone wants to improve that - you are welcome!
function replaceTextCG(text0, re, to) {
var res, pos_f, pos_l;
var matches = text0.getText().match(re);
var count = (matches || []).length;
to = to.replace(/(\$\d+)/g, ',$1,').replace(/^,/, '').replace(/,$/, '').split(",");
for (var i = 0; i < count; i++) {
res = re.exec(text0.getText())
for (var j = 1; j < res.length - 1; j++) {
pos_f = res.index + res[j].length;
pos_l = re.lastIndex - res[j + 1].length - 1;
text0.deleteText(pos_f, pos_l);
text0.insertText(pos_f, to[1]);
}
}
return count;
}
function RegExp_test() {
var docParagraphs = DocumentApp.getActiveDocument().getBody().getParagraphs();
var i = 0, text0, count;
// equivalent of .asText() ???
text0 = docParagraphs[i].editAsText(); // obj
if (text0.getText() !== '') {
count = replaceTextCG(text0, /(?:([Ww]o)\d(rd))/g, '$1A$2');
count = replaceTextCG(text0, /(?:([Ss]o)\d(me))/g, '$1B$2');
count = replaceTextCG(text0, /(?:([Hh]e)\d(re))/g, '$1C$2');
}
Logger.log('Test finished')
}
I want to replace a word "allowance" with "Some text", after running the code, It will remove word allowance and apply "Some text" with same formatting as that of "allowance" but foreground color property is not getting set as that of original.I want Some text also in red color as shown in the screenshot
function retainFormatting() {
var doc = DocumentApp.getActiveDocument();
var textToHighlight = 'allowance';
var highlightStyle;
var paras = doc.getParagraphs();
var textLocation = {};
var i;
for (i=0; i<paras.length; ++i) {
textLocation = paras[i].findText(textToHighlight);
if (textLocation != null && textLocation.getStartOffset() != -1) {
highlightStyle = textLocation.getElement().getAttributes(textLocation.getStartOffset());
textLocation.getElement().deleteText(textLocation.getStartOffset(),textLocation.getEndOffsetInclusive());
textLocation.getElement().insertText(textLocation.getStartOffset(),"Some text");
textLocation.getElement().setAttributes(textLocation.getStartOffset(),textLocation.getEndOffsetInclusive(), highlightStyle);
}
}
}
before setting attribute at offset
after setting attribute it turns out to be
getForegroundColor(offset)
Retrieves the foreground color at the specified character offset.
And
setForegroundColor(startOffset, endOffsetInclusive, color)
Sets the foreground color for the specified character range.
Here is a sample code :
Getting Color from text
highlightColor = textLocation.getElement().getForegroundColor(textLocation.getStartOffset());
Applying color to text
textLocation.getElement().setForegroundColor(textLocation.getStartOffset(),textLocation.getEndOffsetInclusive(), highlightStyle);
I hope it helps. Goodluck :)
Try
textLocation.getElement().editAsText().deleteText(textLocation.getStartOffset(),textLocation.getEndOffsetInclusive());
textLocation.getElement().editAsText().insertText(textLocation.getStartOffset(),"Some text");
The .editAsText() puts you into editing the contents of the rich text leaving the existing attributes as a 'wrapper'
Alternatively, try replacing the text rather than deleting and inserting
paras[i].replaceText("allowance", "some text") // the first attribute is a regular expression as string
I have just tested this and it seems that setting LINK_URL alongside other attributes interferes with FOREGROUND_COLOR.
The following results in a black text color:
var attrs = {
"FOREGROUND_COLOR": "#ff0000", // should be red
"LINK_URL": null
};
text.setAttributes(start, end, attrs);
The following results in a red text color:
var attrs = {
"FOREGROUND_COLOR": "#ff0000" // should be red
};
text.setAttributes(start, end, attrs);
In effect, if you don't need to set the link, remove the LINK_URL from the list of formatting options.
#JSDBroughton Gave me an idea, which worked.
Try setting the attributes of the rich text object you get when calling editAsText. So instead of:
highlightStyle = textLocation.getElement().getAttributes(textLocation.getStartOffset());
textLocation.getElement().setAttributes(textLocation.getStartOffset(),textLocation.getEndOffsetInclusive(), highlightStyle);
Do:
// Make sure you replace `asParagraph` with what you actually need
highlightStyle = textLocation.getElement().asParagraph().editAsText().getAttributes(textLocation.getStartOffset());
textLocation.getElement().asParagraph().editAsText().setAttributes(textLocation.getStartOffset(),textLocation.getEndOffsetInclusive(), highlightStyle);
Edit: after playing around with this, seems like this only sometimes works. I still haven't figured out the pattern for when it does work and when it doesn't.
I'm having an issue with applying textformat to various parts of a string:
feedBackText = "This is <b>bold</b>, and this is some more text, and <b>this is bold too</b>. But this is not bold. This is <b>bold</b>!";
feedbackTextField.htmlText = feedBackText;
var startBoldPos:int = 0;
var closeBoldPos:int = 0;
var i:uint = 0;
while(true) {
startBoldPos = feedBackText.indexOf("<b>", startBoldPos);
closeBoldPos = feedBackText.indexOf("</b>", startBoldPos);
if(startBoldPos > 0) {
i++;
// Here is the main trouble:
feedbackTextField.setTextFormat(_boldFormat, startBoldPos-((7)*i), closeBoldPos-((10)*i));
trace("i is: " + i);
trace("Feedbacktext: " + feedBackText);
trace("Start bold: " + startBoldPos);
trace("End bold: " + closeBoldPos + "\n");
} else {
// This works as expected
feedbackTextField.setTextFormat(_boldFormat, startBoldPos, closeBoldPos-3);
// trace("Feedbacktext: " + feedBackText);
// trace("Start bold: " + startBoldPos);
// trace("End bold: " + closeBoldPos + "\n");
}
if(startBoldPos == -1) break;
startBoldPos = closeBoldPos;
}
I'm trying to play around with the index of where the setTextFormat should be assigned, but it doesn't seem to align with startBoldPos and endBoldPos. Even if the traces are showing the correct numbers of where to place setTextFormat it in the string.
Any ideas would be apppreciated!
Regards,
Hans Magnus
I tested your code and it works as expected. I don't fully understand what you trying to do, so here some general remarks:
You can set some formatting without setTextFormat only with htmlText. After assigning text with html tags textField text will be already partly formatted.
setTextFormat works with text property, so start and end index calculated depend on text without html tags. In your case it will be: This is bold, and this is some more text, and this is bold too. But this is not bold. This is bold!
And tracing your code step by step:
1) Setting text with to htmlText property in TextField. After this TextField contained:
This is bold, and this is some more text, and this is bold too. But this is not bold. This is bold!
2) Loop starts. First iteration:
startBoldPos-((7)*i) = 1
closeBoldPos-((10)*i) = 5
TextField: This is bold, and this is some more text, and this is bold too. But this is not bold. This is bold!
3) Second iteration:
startBoldPos-((7)*i) = 39
closeBoldPos-((10)*i) = 52
TextField: This is bold, and this is some more text, and this is bold too. But this is not bold. This is bold!
4) Third iteration:
startBoldPos-((7)*i) = 87
closeBoldPos-((10)*i) = 85
Nothing changed bacause endPosition < startPosition.
5) Fourth iteration:
startBoldPos = -1
closeBoldPos-3 = 12
TextField: This is bold, and this is some more text, and this is bold too. But this is not bold. This is bold!
And final result on screenshot:
UPDATE (formatting without setTextFormat method):
...
[Embed(source="GOTHIC.TTF", fontName="Gothic", embedAsCFF="false", advancedAntiAliasing="true")]
private var gothicFont:Class;
[Embed(source="GOTHICB.TTF", fontName="Gothic", embedAsCFF="false", advancedAntiAliasing="true", fontWeight="bold")]
private var gothicFontBold:Class;
...
var feedBackText:String = "This is <b>bold</b>, and this is some more text, and <b>this is bold too</b>. But this is not bold. This is <b>bold</b>!";
var feedbackTextField:TextField = new TextField();
feedbackTextField.defaultTextFormat = new TextFormat("Gothic", 14);
feedbackTextField.embedFonts = true;
feedbackTextField.width = 500;
feedbackTextField.htmlText = feedBackText;
And result (with embed font as you see):